From 8e61d77497e09180d0036d4c73ce5f387eb2604c Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 23 Mar 2023 17:49:36 +0300 Subject: [PATCH 001/278] added new controls elements (BasicButtonType, ImageButtonType, LabelWithButtonType, TextFieldWithHeaderType) --- client/fonts/pt-root-ui_vf.ttf | Bin 0 -> 266616 bytes client/images/controls/arrow-right.svg | 4 + client/images/controls/chevron-right.svg | 3 + client/resources.qrc | 8 ++ client/ui/pages.h | 2 +- client/ui/qml/Controls2/BasicButtonType.qml | 57 ++++++++++ client/ui/qml/Controls2/ImageButtonType.qml | 44 ++++++++ .../ui/qml/Controls2/LabelWithButtonType.qml | 43 ++++++++ .../qml/Controls2/TextFieldWithHeaderType.qml | 68 ++++++++++++ client/ui/qml/Pages/PageTest.qml | 104 ++++++++++++++++++ client/ui/qml/Pages/PageVPN.qml | 2 +- 11 files changed, 333 insertions(+), 2 deletions(-) create mode 100644 client/fonts/pt-root-ui_vf.ttf create mode 100644 client/images/controls/arrow-right.svg create mode 100644 client/images/controls/chevron-right.svg create mode 100644 client/ui/qml/Controls2/BasicButtonType.qml create mode 100644 client/ui/qml/Controls2/ImageButtonType.qml create mode 100644 client/ui/qml/Controls2/LabelWithButtonType.qml create mode 100644 client/ui/qml/Controls2/TextFieldWithHeaderType.qml create mode 100644 client/ui/qml/Pages/PageTest.qml diff --git a/client/fonts/pt-root-ui_vf.ttf b/client/fonts/pt-root-ui_vf.ttf new file mode 100644 index 0000000000000000000000000000000000000000..34e6f1f9b241fbfdc7e430a84ccbc394aabe68fa GIT binary patch literal 266616 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 KPo&rZV+Q~OH^k-u literal 0 HcmV?d00001 diff --git a/client/images/controls/arrow-right.svg b/client/images/controls/arrow-right.svg new file mode 100644 index 00000000..69b21b89 --- /dev/null +++ b/client/images/controls/arrow-right.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/images/controls/chevron-right.svg b/client/images/controls/chevron-right.svg new file mode 100644 index 00000000..726eabac --- /dev/null +++ b/client/images/controls/chevron-right.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/resources.qrc b/client/resources.qrc index 67937bda..7a157e5a 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -165,5 +165,13 @@ ui/qml/Controls/PopupWithQuestion.qml ui/qml/Pages/PageAdvancedServerSettings.qml ui/qml/Controls/PopupWarning.qml + ui/qml/Controls2/BasicButtonType.qml + ui/qml/Controls2/TextFieldWithHeaderType.qml + ui/qml/Pages/PageTest.qml + fonts/pt-root-ui_vf.ttf + ui/qml/Controls2/LabelWithButtonType.qml + images/controls/arrow-right.svg + images/controls/chevron-right.svg + ui/qml/Controls2/ImageButtonType.qml diff --git a/client/ui/pages.h b/client/ui/pages.h index dfc9a509..a639d63b 100644 --- a/client/ui/pages.h +++ b/client/ui/pages.h @@ -24,7 +24,7 @@ enum class Page {Start = 0, NewServer, NewServerProtocols, Vpn, Wizard, WizardLow, WizardMedium, WizardHigh, WizardVpnMode, ServerConfiguringProgress, GeneralSettings, AppSettings, NetworkSettings, ServerSettings, ServerContainers, ServersList, ShareConnection, Sites, - ProtocolSettings, ProtocolShare, QrDecoder, QrDecoderIos, About, ViewConfig, AdvancedServerSettings}; + ProtocolSettings, ProtocolShare, QrDecoder, QrDecoderIos, About, ViewConfig, AdvancedServerSettings, Test}; Q_ENUM_NS(Page) static void declareQmlPageEnum() { diff --git a/client/ui/qml/Controls2/BasicButtonType.qml b/client/ui/qml/Controls2/BasicButtonType.qml new file mode 100644 index 00000000..7133d236 --- /dev/null +++ b/client/ui/qml/Controls2/BasicButtonType.qml @@ -0,0 +1,57 @@ +import QtQuick +import QtQuick.Controls + +Button { + id: root + + property string hoveredColor: "#C1C2C5" + property string defaultColor: "#D7D8DB" + property string disabledColor: "#494B50" + property string pressedColor: "#979799" + + property string textColor: "#0E0E11" + + property string borderColor: "#D7D8DB" + property int borderWidth: 0 + + implicitWidth: 328 + implicitHeight: 56 + + hoverEnabled: true + + background: Rectangle { + id: background + anchors.fill: parent + radius: 16 + color: { + if (root.enabled) { + if(root.pressed) { + return pressedColor + } + return hovered ? hoveredColor : defaultColor + } else { + return disabledColor + } + } + border.color: borderColor + border.width: borderWidth + } + + MouseArea { + anchors.fill: background + enabled: false + cursorShape: Qt.PointingHandCursor + } + + contentItem: Text { + anchors.fill: background + font.family: "PT Root UI" + font.styleName: "normal" + font.weight: 400 + font.pixelSize: 16 + color: textColor + text: root.text + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } +} diff --git a/client/ui/qml/Controls2/ImageButtonType.qml b/client/ui/qml/Controls2/ImageButtonType.qml new file mode 100644 index 00000000..be1bd0e4 --- /dev/null +++ b/client/ui/qml/Controls2/ImageButtonType.qml @@ -0,0 +1,44 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Button { + id: root + + property string image + + property string hoveredColor: Qt.rgba(255, 255, 255, 0.08) + property string defaultColor: Qt.rgba(255, 255, 255, 0) + property string pressedColor: Qt.rgba(255, 255, 255, 0.12) + + property string imageColor: "#878B91" + + implicitWidth: 40 + implicitHeight: 40 + + hoverEnabled: true + + icon.source: image + icon.color: imageColor + + background: Rectangle { + id: background + + anchors.fill: parent + radius: 12 + color: { + if (root.enabled) { + if(root.pressed) { + return pressedColor + } + return hovered ? hoveredColor : defaultColor + } + } + } + + MouseArea { + anchors.fill: parent + enabled: false + cursorShape: Qt.PointingHandCursor + } +} diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml new file mode 100644 index 00000000..c49d1e06 --- /dev/null +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -0,0 +1,43 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + id: root + + property string text + + property var onClickedFunc + property alias buttonImage : button.image + + implicitWidth: 360 + implicitHeight: 72 + + RowLayout { + anchors.fill: parent + + Text { + font.family: "PT Root UI" + font.styleName: "normal" + font.pixelSize: 18 + color: "#d7d8db" + text: root.text + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + } + + ImageButtonType { + id: button + + image: buttonImage + onClicked: { + if (onClickedFunc && typeof onClickedFunc === "function") { + onClickedFunc() + } + } + + Layout.alignment: Qt.AlignRight + } + } + +} diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml new file mode 100644 index 00000000..18f62344 --- /dev/null +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -0,0 +1,68 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + id: root + + property string headerText + property string textFieldText + property bool textFieldEditable: true + + implicitWidth: 328 + implicitHeight: 74 + + Rectangle { + id: backgroud + anchors.fill: parent + color: "#1c1d21" + radius: 16 + border.color: "#d7d8db" + border.width: textField.focus ? 1 : 0 + } + + ColumnLayout { + anchors.fill: backgroud + + Text { + text: root.headerText + color: "#878b91" + font.pixelSize: 13 + font.weight: 400 + font.family: "PT Root UI VF" + font.letterSpacing: 0.02 + + height: 16 + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.topMargin: 16 + } + + TextField { + id: textField + + enabled: root.textFieldEditable + text: root.textFieldText + color: "#d7d8db" + font.pixelSize: 16 + font.weight: 400 + font.family: "PT Root UI VF" + + height: 24 + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.bottomMargin: 16 + topPadding: 0 + rightPadding: 0 + leftPadding: 0 + bottomPadding: 0 + + background: Rectangle { + anchors.fill: parent + color: "#1c1d21" + } + } + } +} diff --git a/client/ui/qml/Pages/PageTest.qml b/client/ui/qml/Pages/PageTest.qml new file mode 100644 index 00000000..11b3ff1d --- /dev/null +++ b/client/ui/qml/Pages/PageTest.qml @@ -0,0 +1,104 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import PageEnum 1.0 +import "./" +import "../Controls" +import "../Controls2" +import "../Config" + +PageBase { + id: root + page: PageEnum.Test + logic: ViewConfigLogic + + Rectangle { + y: 0 + anchors.fill: parent + color: "#0E0E11" + } + + FlickableType { + id: fl + anchors.top: root.top + anchors.bottom: root.bottom + contentHeight: content.height + + ColumnLayout { + id: content + enabled: true + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 15 + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 10 + text: qsTr("Forget this server") + +// onClicked: { +// UiLogic.goToPage(PageEnum.Start) +// } + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 10 + + defaultColor: "#0E0E11" + hoveredColor: Qt.rgba(255, 255, 255, 0.08) + pressedColor: Qt.rgba(255, 255, 255, 0.12) + disabledColor: "#878B91" + textColor: "#D7D8DB" + borderWidth: 1 + + text: qsTr("Forget this server") + +// onClicked: { +// UiLogic.goToPage(PageEnum.Start) +// } + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + Layout.topMargin: 10 + headerText: "Server IP adress [:port]" + } + + LabelWithButtonType { + id: ip + Layout.fillWidth: true + Layout.topMargin: 10 + + text: "IP, логин и пароль от сервера" + buttonImage: "qrc:/images/controls/chevron-right.svg" + +// onClickedFunc: function() { +// UiLogic.goToPage(PageEnum.Start) +// } + } + Rectangle { + Layout.fillWidth: true + height: 1 + color: "#2C2D30" + } + LabelWithButtonType { + Layout.fillWidth: true + Layout.topMargin: 10 + + text: "QR-код, ключ или файл настроек" + buttonImage: "qrc:/images/controls/chevron-right.svg" + +// onClickedFunc: function() { +// UiLogic.goToPage(PageEnum.Start) +// } + } + Rectangle { + Layout.fillWidth: true + height: 1 + color: "#2C2D30" + } + } + } +} diff --git a/client/ui/qml/Pages/PageVPN.qml b/client/ui/qml/Pages/PageVPN.qml index 342fb129..0e6f5078 100644 --- a/client/ui/qml/Pages/PageVPN.qml +++ b/client/ui/qml/Pages/PageVPN.qml @@ -39,7 +39,7 @@ PageBase { label.text: qsTr("Donate") onClicked: { - UiLogic.goToPage(PageEnum.About) + UiLogic.goToPage(PageEnum.Test) } } From 167d57408dea255c1e881356192dbd4698181c77 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 6 Apr 2023 16:33:53 +0300 Subject: [PATCH 002/278] added CardType component - added transition for BasicButtonType --- client/resources.qrc | 1 + client/ui/qml/Controls2/BasicButtonType.qml | 4 + client/ui/qml/Controls2/CardType.qml | 118 ++++++++++++++++++++ client/ui/qml/Pages/PageTest.qml | 20 +++- 4 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 client/ui/qml/Controls2/CardType.qml diff --git a/client/resources.qrc b/client/resources.qrc index 7a157e5a..9280e2b2 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -173,5 +173,6 @@ images/controls/arrow-right.svg images/controls/chevron-right.svg ui/qml/Controls2/ImageButtonType.qml + ui/qml/Controls2/CardType.qml diff --git a/client/ui/qml/Controls2/BasicButtonType.qml b/client/ui/qml/Controls2/BasicButtonType.qml index 7133d236..cbb664e4 100644 --- a/client/ui/qml/Controls2/BasicButtonType.qml +++ b/client/ui/qml/Controls2/BasicButtonType.qml @@ -35,6 +35,10 @@ Button { } border.color: borderColor border.width: borderWidth + + Behavior on color { + PropertyAnimation { duration: 200 } + } } MouseArea { diff --git a/client/ui/qml/Controls2/CardType.qml b/client/ui/qml/Controls2/CardType.qml new file mode 100644 index 00000000..cd7320be --- /dev/null +++ b/client/ui/qml/Controls2/CardType.qml @@ -0,0 +1,118 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +RadioButton { + id: root + + property string headerText + property string bodyText + property string footerText + + property string hoveredColor: Qt.rgba(255, 255, 255, 0) + property string defaultColor: Qt.rgba(255, 255, 255, 0.05) + property string disabledColor: Qt.rgba(255, 255, 255, 0) + property string pressedColor: Qt.rgba(255, 255, 255, 0.05) + + property string textColor: "#0E0E11" + + property string pressedBorderColor: Qt.rgba(251, 178, 106, 0.3) + property string hoveredBorderColor: Qt.rgba(251, 178, 106, 1) + property int borderWidth: 0 + + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight + + hoverEnabled: true + + indicator: Rectangle { + anchors.fill: parent + radius: 16 + + color: { + if (root.enabled) { + if(root.checked) { + return pressedColor + } + return hovered ? hoveredColor : defaultColor + } else { + return disabledColor + } + } + border.color: { + if (root.enabled) { + if(root.checked) { + return pressedBorderColor + } + return hovered ? hoveredBorderColor : defaultColor + } else { + return disabledColor + } + } + border.width: { + if (root.enabled) { + if(root.checked) { + return 1 + } + return hovered ? 1 : 0 + } else { + return 0 + } + } + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + + ColumnLayout { + id: content + anchors.fill: parent + spacing: 16 + + Text { + text: root.headerText + color: "#878b91" + font.pixelSize: 25 + font.weight: 700 + font.family: "PT Root UI VF" + + height: 30 + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.topMargin: 16 + } + + Text { + text: root.bodyText + wrapMode: Text.WordWrap + color: "#878b91" + font.pixelSize: 16 + font.weight: 400 + font.family: "PT Root UI VF" + + height: 24 + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.bottomMargin: root.footerText !== "" ? 0 : 16 + } + + Text { + text: root.footerText + visible: root.footerText !== "" + enabled: root.footerText !== "" + color: "#878b91" + font.pixelSize: 13 + font.weight: 400 + font.family: "PT Root UI VF" + + height: 16 + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.bottomMargin: 16 + } + } +} diff --git a/client/ui/qml/Pages/PageTest.qml b/client/ui/qml/Pages/PageTest.qml index 11b3ff1d..dc3cc69b 100644 --- a/client/ui/qml/Pages/PageTest.qml +++ b/client/ui/qml/Pages/PageTest.qml @@ -46,7 +46,7 @@ PageBase { Layout.fillWidth: true Layout.topMargin: 10 - defaultColor: "#0E0E11" + defaultColor: "transparent" hoveredColor: Qt.rgba(255, 255, 255, 0.08) pressedColor: Qt.rgba(255, 255, 255, 0.12) disabledColor: "#878B91" @@ -99,6 +99,24 @@ PageBase { height: 1 color: "#2C2D30" } + + CardType { + Layout.fillWidth: true + Layout.topMargin: 10 + + headerText: "Высокий" + bodyText: "Многие иностранные сайты и VPN-провайдеры заблокированы" + footerText: "футер" + } + + CardType { + Layout.fillWidth: true + Layout.topMargin: 10 + + headerText: "Высокий" + bodyText: "Многие иностранные сайты и VPN-провайдеры заблокированы" + footerText: "футер" + } } } } From c74c5e0c6d137209985baf2ecf735edc70e63171 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 7 Apr 2023 20:50:55 +0300 Subject: [PATCH 003/278] added CheckBoxType - added hover effect to LabelWithButtonType --- client/images/controls/check.svg | 3 + client/resources.qrc | 2 + client/ui/qml/Controls2/CardType.qml | 19 ++--- client/ui/qml/Controls2/CheckBoxType.qml | 77 +++++++++++++++++++ client/ui/qml/Controls2/ImageButtonType.qml | 5 +- .../ui/qml/Controls2/LabelWithButtonType.qml | 33 ++++++++ client/ui/qml/Pages/PageTest.qml | 4 + 7 files changed, 133 insertions(+), 10 deletions(-) create mode 100644 client/images/controls/check.svg create mode 100644 client/ui/qml/Controls2/CheckBoxType.qml diff --git a/client/images/controls/check.svg b/client/images/controls/check.svg new file mode 100644 index 00000000..16b4c0da --- /dev/null +++ b/client/images/controls/check.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/resources.qrc b/client/resources.qrc index 9280e2b2..bda89bf2 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -174,5 +174,7 @@ images/controls/chevron-right.svg ui/qml/Controls2/ImageButtonType.qml ui/qml/Controls2/CardType.qml + ui/qml/Controls2/CheckBoxType.qml + images/controls/check.svg diff --git a/client/ui/qml/Controls2/CardType.qml b/client/ui/qml/Controls2/CardType.qml index cd7320be..31b51f31 100644 --- a/client/ui/qml/Controls2/CardType.qml +++ b/client/ui/qml/Controls2/CardType.qml @@ -9,15 +9,16 @@ RadioButton { property string bodyText property string footerText - property string hoveredColor: Qt.rgba(255, 255, 255, 0) - property string defaultColor: Qt.rgba(255, 255, 255, 0.05) + property string hoveredColor: Qt.rgba(255, 255, 255, 0.05) + property string defaultColor: Qt.rgba(255, 255, 255, 0) property string disabledColor: Qt.rgba(255, 255, 255, 0) property string pressedColor: Qt.rgba(255, 255, 255, 0.05) property string textColor: "#0E0E11" property string pressedBorderColor: Qt.rgba(251, 178, 106, 0.3) - property string hoveredBorderColor: Qt.rgba(251, 178, 106, 1) + property string hoveredBorderColor: "transparent" + property string defaultBodredColor: "#FBB26A" property int borderWidth: 0 implicitWidth: content.implicitWidth @@ -44,9 +45,9 @@ RadioButton { if(root.checked) { return pressedBorderColor } - return hovered ? hoveredBorderColor : defaultColor + return hovered ? hoveredBorderColor : defaultBodredColor } else { - return disabledColor + return defaultBodredColor } } border.width: { @@ -54,7 +55,7 @@ RadioButton { if(root.checked) { return 1 } - return hovered ? 1 : 0 + return hovered ? 0 : 1 } else { return 0 } @@ -72,7 +73,7 @@ RadioButton { Text { text: root.headerText - color: "#878b91" + color: "#D7D8DB" font.pixelSize: 25 font.weight: 700 font.family: "PT Root UI VF" @@ -87,7 +88,7 @@ RadioButton { Text { text: root.bodyText wrapMode: Text.WordWrap - color: "#878b91" + color: "#D7D8DB" font.pixelSize: 16 font.weight: 400 font.family: "PT Root UI VF" @@ -103,7 +104,7 @@ RadioButton { text: root.footerText visible: root.footerText !== "" enabled: root.footerText !== "" - color: "#878b91" + color: "#878B91" font.pixelSize: 13 font.weight: 400 font.family: "PT Root UI VF" diff --git a/client/ui/qml/Controls2/CheckBoxType.qml b/client/ui/qml/Controls2/CheckBoxType.qml new file mode 100644 index 00000000..1cd46143 --- /dev/null +++ b/client/ui/qml/Controls2/CheckBoxType.qml @@ -0,0 +1,77 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + id: root + + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight + + RowLayout { + id: content + + anchors.fill: parent + + CheckBox { + id: checkBox + + implicitWidth: 56 + implicitHeight: 56 + + indicator: Image { + id: indicator + anchors.verticalCenter: checkBox.verticalCenter + anchors.horizontalCenter: checkBox.horizontalCenter + source: checkBox.checked ? "qrc:/images/controls/check.svg" : "" + } + Rectangle { + anchors.verticalCenter: checkBox.verticalCenter + anchors.horizontalCenter: checkBox.horizontalCenter + width: 24 + height: 24 + color: "transparent" + border.color: "#FBB26A" + border.width: 1 + radius: 4 + } + background: Rectangle { + id: background + color: Qt.rgba(255, 255, 255, 0.05) + radius: 16 + } + } + + ColumnLayout { + Text { + text: "Paragraph" + color: "#D7D8DB" + font.pixelSize: 18 + font.weight: 400 + font.family: "PT Root UI VF" + + height: 22 + Layout.fillWidth: true + } + + Text { + text: "Caption" + color: "#878b91" + font.pixelSize: 13 + font.weight: 400 + font.family: "PT Root UI VF" + font.letterSpacing: 0.02 + + height: 16 + Layout.fillWidth: true + } + } + } + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + checkBox.checked = !checkBox.checked + } + } +} diff --git a/client/ui/qml/Controls2/ImageButtonType.qml b/client/ui/qml/Controls2/ImageButtonType.qml index be1bd0e4..b6262906 100644 --- a/client/ui/qml/Controls2/ImageButtonType.qml +++ b/client/ui/qml/Controls2/ImageButtonType.qml @@ -8,7 +8,7 @@ Button { property string image property string hoveredColor: Qt.rgba(255, 255, 255, 0.08) - property string defaultColor: Qt.rgba(255, 255, 255, 0) + property string defaultColor: "transparent" property string pressedColor: Qt.rgba(255, 255, 255, 0.12) property string imageColor: "#878B91" @@ -34,6 +34,9 @@ Button { return hovered ? hoveredColor : defaultColor } } + Behavior on color { + PropertyAnimation { duration: 200 } + } } MouseArea { diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml index c49d1e06..5da4db5f 100644 --- a/client/ui/qml/Controls2/LabelWithButtonType.qml +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -29,6 +29,7 @@ Item { ImageButtonType { id: button + hoverEnabled: false image: buttonImage onClicked: { if (onClickedFunc && typeof onClickedFunc === "function") { @@ -37,7 +38,39 @@ Item { } Layout.alignment: Qt.AlignRight + + Rectangle { + id: imageBackground + anchors.fill: button + radius: 12 + color: "transparent" + + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } } } + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + onEntered: { + imageBackground.color = button.hoveredColor + } + + onExited: { + imageBackground.color = button.defaultColor + } + + onPressedChanged: { + imageBackground.color = pressed ? button.pressedColor : entered ? button.hoveredColor : button.defaultColor + } + + onClicked: { + button.clicked() + } + } } diff --git a/client/ui/qml/Pages/PageTest.qml b/client/ui/qml/Pages/PageTest.qml index dc3cc69b..9f820389 100644 --- a/client/ui/qml/Pages/PageTest.qml +++ b/client/ui/qml/Pages/PageTest.qml @@ -117,6 +117,10 @@ PageBase { bodyText: "Многие иностранные сайты и VPN-провайдеры заблокированы" footerText: "футер" } + + CheckBoxType { +// text: qsTr("Auto-negotiate encryption") + } } } } From ec96c1b53437e77d12a48f31f8442dd83e0a20bb Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 10 Apr 2023 06:43:36 +0300 Subject: [PATCH 004/278] added hover and pressed effects for CheckBoxType.qml --- client/ui/qml/Controls2/CheckBoxType.qml | 54 ++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/client/ui/qml/Controls2/CheckBoxType.qml b/client/ui/qml/Controls2/CheckBoxType.qml index 1cd46143..c547a8e4 100644 --- a/client/ui/qml/Controls2/CheckBoxType.qml +++ b/client/ui/qml/Controls2/CheckBoxType.qml @@ -1,10 +1,24 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import Qt5Compat.GraphicalEffects Item { id: root + property string hoveredColor: Qt.rgba(255, 255, 255, 0.05) + property string defaultColor: "transparent" + property string pressedColor: Qt.rgba(255, 255, 255, 0.05) + + property string defaultBorderColor: "#D7D8DB" + property string checkedBorderColor: "#FBB26A" + + property string checkedImageColor: "#FBB26A" + property string hoveredImageColor: "#A85809" + property string defaultImageColor: "transparent" + + property string imageSource: "qrc:/images/controls/check.svg" + implicitWidth: content.implicitWidth implicitHeight: content.implicitHeight @@ -23,22 +37,35 @@ Item { id: indicator anchors.verticalCenter: checkBox.verticalCenter anchors.horizontalCenter: checkBox.horizontalCenter - source: checkBox.checked ? "qrc:/images/controls/check.svg" : "" + ColorOverlay { + id: imageColor + anchors.fill: indicator + source: indicator + } } + Rectangle { + id: imageBorder + anchors.verticalCenter: checkBox.verticalCenter anchors.horizontalCenter: checkBox.horizontalCenter width: 24 height: 24 color: "transparent" - border.color: "#FBB26A" + border.color: checkBox.checked ? checkedBorderColor : defaultBorderColor border.width: 1 radius: 4 } + background: Rectangle { - id: background - color: Qt.rgba(255, 255, 255, 0.05) + id: checkBoxBackground radius: 16 + + color: "transparent" + + Behavior on color { + PropertyAnimation { duration: 200 } + } } } @@ -70,8 +97,27 @@ Item { MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor + hoverEnabled: true + + onEntered: { + checkBoxBackground.color = hoveredColor + } + + onExited: { + checkBoxBackground.color = defaultColor + } + + onPressedChanged: { + indicator.source = pressed ? imageSource : "" + imageColor.color = pressed ? hoveredImageColor : defaultImageColor + checkBoxBackground.color = pressed ? pressedColor : entered ? hoveredColor : defaultColor + } + onClicked: { checkBox.checked = !checkBox.checked + indicator.source = checkBox.checked ? imageSource : "" + imageColor.color = checkBox.checked ? checkedImageColor : defaultImageColor + imageBorder.border.color = checkBox.checked ? checkedBorderColor : defaultBorderColor } } } From 905a3a30f37b299580dd94fdba818997085303be Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 12 Apr 2023 19:13:41 +0300 Subject: [PATCH 005/278] added some new controls and started layout of pageStart and pageCredentials --- client/amnezia_application.cpp | 2 +- client/images/amneziaBigLogo.png | Bin 0 -> 25211 bytes client/images/amneziaBigLogo.svg | 1 + client/images/controls/arrow-left.svg | 4 + client/resources.qrc | 12 ++ client/ui/pages.h | 2 +- client/ui/qml/Controls2/BodyTextType.qml | 12 ++ client/ui/qml/Controls2/DropDownType.qml | 5 + client/ui/qml/Controls2/FlickableType.qml | 23 +++ client/ui/qml/Controls2/Header2TextType.qml | 10 ++ client/ui/qml/Controls2/HeaderTextType.qml | 40 +++++ .../qml/Controls2/TextFieldWithHeaderType.qml | 4 + client/ui/qml/PageLoader.qml | 57 ++++++ client/ui/qml/Pages/PageTest.qml | 1 - client/ui/qml/Pages2/PageCredentials.qml | 83 +++++++++ client/ui/qml/Pages2/PageStart.qml | 163 ++++++++++++++++++ client/ui/qml/main.qml | 1 - client/ui/qml/main2.qml | 142 +++++++++++++++ 18 files changed, 558 insertions(+), 4 deletions(-) create mode 100644 client/images/amneziaBigLogo.png create mode 100644 client/images/amneziaBigLogo.svg create mode 100644 client/images/controls/arrow-left.svg create mode 100644 client/ui/qml/Controls2/BodyTextType.qml create mode 100644 client/ui/qml/Controls2/DropDownType.qml create mode 100644 client/ui/qml/Controls2/FlickableType.qml create mode 100644 client/ui/qml/Controls2/Header2TextType.qml create mode 100644 client/ui/qml/Controls2/HeaderTextType.qml create mode 100644 client/ui/qml/PageLoader.qml create mode 100644 client/ui/qml/Pages2/PageCredentials.qml create mode 100644 client/ui/qml/Pages2/PageStart.qml create mode 100644 client/ui/qml/main2.qml diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 588854d3..3fb76be2 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -92,7 +92,7 @@ void AmneziaApplication::init() m_engine = new QQmlApplicationEngine; m_uiLogic = new UiLogic(m_settings, m_configurator, m_serverController); - const QUrl url(QStringLiteral("qrc:/ui/qml/main.qml")); + const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml")); QObject::connect(m_engine, &QQmlApplicationEngine::objectCreated, this, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) diff --git a/client/images/amneziaBigLogo.png b/client/images/amneziaBigLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..35a45f3b78807c74143c2fd3b430b8e406880448 GIT binary patch literal 25211 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 literal 0 HcmV?d00001 diff --git a/client/images/amneziaBigLogo.svg b/client/images/amneziaBigLogo.svg new file mode 100644 index 00000000..c50c7743 --- /dev/null +++ b/client/images/amneziaBigLogo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/images/controls/arrow-left.svg b/client/images/controls/arrow-left.svg new file mode 100644 index 00000000..98c9950b --- /dev/null +++ b/client/images/controls/arrow-left.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/resources.qrc b/client/resources.qrc index bda89bf2..f95ef585 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -176,5 +176,17 @@ ui/qml/Controls2/CardType.qml ui/qml/Controls2/CheckBoxType.qml images/controls/check.svg + ui/qml/Controls2/DropDownType.qml + ui/qml/Pages2/PageStart.qml + ui/qml/main2.qml + ui/qml/PageLoader.qml + images/amneziaBigLogo.png + images/amneziaBigLogo.svg + ui/qml/Controls2/BodyTextType.qml + ui/qml/Controls2/FlickableType.qml + ui/qml/Controls2/Header2TextType.qml + ui/qml/Pages2/PageCredentials.qml + ui/qml/Controls2/HeaderTextType.qml + images/controls/arrow-left.svg diff --git a/client/ui/pages.h b/client/ui/pages.h index a639d63b..c33289bb 100644 --- a/client/ui/pages.h +++ b/client/ui/pages.h @@ -24,7 +24,7 @@ enum class Page {Start = 0, NewServer, NewServerProtocols, Vpn, Wizard, WizardLow, WizardMedium, WizardHigh, WizardVpnMode, ServerConfiguringProgress, GeneralSettings, AppSettings, NetworkSettings, ServerSettings, ServerContainers, ServersList, ShareConnection, Sites, - ProtocolSettings, ProtocolShare, QrDecoder, QrDecoderIos, About, ViewConfig, AdvancedServerSettings, Test}; + ProtocolSettings, ProtocolShare, QrDecoder, QrDecoderIos, About, ViewConfig, AdvancedServerSettings, Test, Credentials}; Q_ENUM_NS(Page) static void declareQmlPageEnum() { diff --git a/client/ui/qml/Controls2/BodyTextType.qml b/client/ui/qml/Controls2/BodyTextType.qml new file mode 100644 index 00000000..9d789385 --- /dev/null +++ b/client/ui/qml/Controls2/BodyTextType.qml @@ -0,0 +1,12 @@ +import QtQuick + +Text { + text: root.bodyText + wrapMode: Text.WordWrap + color: "#D7D8DB" + font.pixelSize: 16 + font.weight: 400 + font.family: "PT Root UI VF" + + height: 24 +} diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml new file mode 100644 index 00000000..5560aee7 --- /dev/null +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -0,0 +1,5 @@ +import QtQuick + +Item { + +} diff --git a/client/ui/qml/Controls2/FlickableType.qml b/client/ui/qml/Controls2/FlickableType.qml new file mode 100644 index 00000000..b7c1203f --- /dev/null +++ b/client/ui/qml/Controls2/FlickableType.qml @@ -0,0 +1,23 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import "../Config" + +Flickable { + id: fl + + clip: true + width: parent.width + + anchors.bottom: parent.bottom + anchors.left: root.left + anchors.right: root.right + anchors.rightMargin: 1 + + Keys.onUpPressed: scrollBar.decrease() + Keys.onDownPressed: scrollBar.increase() + + ScrollBar.vertical: ScrollBar { + id: scrollBar + policy: fl.height >= fl.contentHeight ? ScrollBar.AlwaysOff : ScrollBar.AlwaysOn + } +} diff --git a/client/ui/qml/Controls2/Header2TextType.qml b/client/ui/qml/Controls2/Header2TextType.qml new file mode 100644 index 00000000..4bbbc0d6 --- /dev/null +++ b/client/ui/qml/Controls2/Header2TextType.qml @@ -0,0 +1,10 @@ +import QtQuick + +Text { + color: "#D7D8DB" + font.pixelSize: 25 + font.weight: 700 + font.family: "PT Root UI VF" + + height: 30 +} diff --git a/client/ui/qml/Controls2/HeaderTextType.qml b/client/ui/qml/Controls2/HeaderTextType.qml new file mode 100644 index 00000000..60fa4e95 --- /dev/null +++ b/client/ui/qml/Controls2/HeaderTextType.qml @@ -0,0 +1,40 @@ +import QtQuick +import QtQuick.Layouts + +ColumnLayout { + id: root + + property string buttonImage + property string headerText + property var wrapMode + + ImageButtonType { + id: button + + Layout.leftMargin: -6 + + hoverEnabled: false + image: root.buttonImage + onClicked: { + if (onClickedFunc && typeof onClickedFunc === "function") { + onClickedFunc() + } + } + } + + Text { + id: header + + text: root.headerText + + color: "#D7D8DB" + font.pixelSize: 36 + font.weight: 700 + font.family: "PT Root UI VF" + font.letterSpacing: -0.03 + + wrapMode: Text.WordWrap + + height: 38 + } +} diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml index 18f62344..dd0d2907 100644 --- a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -7,6 +7,7 @@ Item { property string headerText property string textFieldText + property string textFieldPlaceholderText property bool textFieldEditable: true implicitWidth: 328 @@ -45,6 +46,9 @@ Item { enabled: root.textFieldEditable text: root.textFieldText color: "#d7d8db" + + placeholderText: textFieldPlaceholderText + font.pixelSize: 16 font.weight: 400 font.family: "PT Root UI VF" diff --git a/client/ui/qml/PageLoader.qml b/client/ui/qml/PageLoader.qml new file mode 100644 index 00000000..5f2fc3c2 --- /dev/null +++ b/client/ui/qml/PageLoader.qml @@ -0,0 +1,57 @@ +import QtQuick + +import Qt.labs.folderlistmodel + +import PageType 1.0 + +Item { + property var pages: ({}) + + signal finished() + + FolderListModel { + id: folderModelPages + folder: "qrc:/ui/qml/Pages2/" + nameFilters: ["*.qml"] + showDirs: false + + onStatusChanged: { + if (status == FolderListModel.Ready) { + for (var i = 0; i < folderModelPages.count; i++) { + createPagesObjects(folderModelPages.get(i, "filePath"), PageType.Basic); + } + finished() + } + } + + function createPagesObjects(file, type) { + if (file.indexOf("Base") !== -1) { + return; // skip Base Pages + } + + var c = Qt.createComponent("qrc" + file); + + var finishCreation = function(component) { + if (component.status === Component.Ready) { + var obj = component.createObject(root); + if (obj === null) { + console.debug("Error creating object " + component.url); + } else { + obj.visible = false + if (type === PageType.Basic) { + pages[obj.page] = obj + } + } + } else if (component.status === Component.Error) { + console.debug("Error loading component:", component.errorString()); + } + } + + if (c.status === Component.Ready) { + finishCreation(c); + } else { + console.debug("Warning: " + file + " page components are not ready " + c.errorString()); + } + } + } +} diff --git a/client/ui/qml/Pages/PageTest.qml b/client/ui/qml/Pages/PageTest.qml index 9f820389..4c571ae0 100644 --- a/client/ui/qml/Pages/PageTest.qml +++ b/client/ui/qml/Pages/PageTest.qml @@ -13,7 +13,6 @@ PageBase { logic: ViewConfigLogic Rectangle { - y: 0 anchors.fill: parent color: "#0E0E11" } diff --git a/client/ui/qml/Pages2/PageCredentials.qml b/client/ui/qml/Pages2/PageCredentials.qml new file mode 100644 index 00000000..509ee6a6 --- /dev/null +++ b/client/ui/qml/Pages2/PageCredentials.qml @@ -0,0 +1,83 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "./" +import "../Pages" +import "../Controls2" +import "../Config" + +PageBase { + id: root + page: PageEnum.Credentials + + FlickableType { + id: fl + 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 + + spacing: 16 + + HeaderTextType { + Layout.fillWidth: true + Layout.bottomMargin: 16 + Layout.topMargin: 66 + + Layout.preferredWidth: 328 + + buttonImage: "qrc:/images/controls/arrow-left.svg" + + headerText: "Подключение к серверу" + wrapMode: Text.WordWrap + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + headerText: "Server IP adress [:port]" + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + headerText: "Login to connect via SSH" + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + headerText: "Password / Private key" + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 40 + + text: qsTr("Настроить сервер простым образом") + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 8 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(255, 255, 255, 0.08) + pressedColor: Qt.rgba(255, 255, 255, 0.12) + disabledColor: "#878B91" + textColor: "#D7D8DB" + borderWidth: 1 + + text: qsTr("Выбрать протокол для установки") + } + } + } +} diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml new file mode 100644 index 00000000..6f8f0356 --- /dev/null +++ b/client/ui/qml/Pages2/PageStart.qml @@ -0,0 +1,163 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "./" +import "../Pages" +import "../Controls2" +import "../Config" + +PageBase { + id: root + page: PageEnum.Start + + FlickableType { + id: fl + 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 + + Image { + id: image + source: "qrc:/images/amneziaBigLogo.png" + + Layout.alignment: Qt.AlignCenter + Layout.topMargin: 80 + Layout.leftMargin: 8 + Layout.rightMargin: 8 + Layout.preferredWidth: 344 + Layout.preferredHeight: 279 + } + + BodyTextType { + Layout.fillWidth: true + Layout.topMargin: 50 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: "Бесплатный сервис для создания личного VPN на вашем сервере. Помогаем получать доступ к заблокированному контенту, не раскрывая конфиденциальность даже провайдерам VPN." + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("У меня есть данные для подключения") + + onClicked: { + drawer.visible = true + } + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 8 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(255, 255, 255, 0.08) + pressedColor: Qt.rgba(255, 255, 255, 0.12) + disabledColor: "#878B91" + textColor: "#D7D8DB" + borderWidth: 1 + + text: qsTr("У меня ничего нет") + +// onClicked: { +// UiLogic.goToPage(PageEnum.Start) +// } + } + } + + Drawer { + id: drawer + + y: 0 + x: 0 + edge: Qt.BottomEdge + width: parent.width + height: parent.height * 0.4375 + + clip: true + + background: Rectangle { + anchors.fill: parent + anchors.bottomMargin: -radius + radius: 16 + color: "#1C1D21" + } + + modal: true + //interactive: activeFocus + +// onAboutToHide: { +// pageLoader.focus = true +// } +// onAboutToShow: { +// tfSshLog.focus = true +// } + + ColumnLayout { + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + Header2TextType { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.alignment: Qt.AlignHCenter + + text: "Данные для подключения" + } + + LabelWithButtonType { + id: ip + Layout.fillWidth: true + Layout.topMargin: 32 + + text: "IP, логин и пароль от сервера" + buttonImage: "qrc:/images/controls/chevron-right.svg" + + onClickedFunc: function() { + UiLogic.goToPage(PageEnum.Credentials) + } + } + Rectangle { + Layout.fillWidth: true + height: 1 + color: "#2C2D30" + } + LabelWithButtonType { + Layout.fillWidth: true + + text: "QR-код, ключ или файл настроек" + buttonImage: "qrc:/images/controls/chevron-right.svg" + + // onClickedFunc: function() { + // UiLogic.goToPage(PageEnum.Start) + // } + } + Rectangle { + Layout.fillWidth: true + height: 1 + color: "#2C2D30" + } + } + } + } +} diff --git a/client/ui/qml/main.qml b/client/ui/qml/main.qml index b8758384..5194073e 100644 --- a/client/ui/qml/main.qml +++ b/client/ui/qml/main.qml @@ -85,7 +85,6 @@ Window { } Rectangle { - y: 0 anchors.fill: parent color: "white" } diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml new file mode 100644 index 00000000..deb64e89 --- /dev/null +++ b/client/ui/qml/main2.qml @@ -0,0 +1,142 @@ +import QtQuick +import QtQuick.Window +import QtQuick.Controls +import QtQuick.Layouts + +import PageType 1.0 + +import "Config" + +Window { + id: root + visible: true + width: GC.screenWidth + height: GC.screenHeight + minimumWidth: GC.isDesktop() ? 360 : 0 + minimumHeight: GC.isDesktop() ? 640 : 0 + onClosing: function() { + console.debug("QML onClosing signal") + UiLogic.onCloseWindow() + } + + title: "AmneziaVPN" + + function gotoPage(type, page, reset, slide) { + let p_obj; + if (type === PageType.Basic) p_obj = pageLoader.pages[page] + else if (type === PageType.Proto) p_obj = protocolPages[page] + else if (type === PageType.ShareProto) p_obj = sharePages[page] + else return + + if (pageStackView.depth > 0) { + pageStackView.currentItem.deactivated() + } + + if (slide) { + pageStackView.push(p_obj, {}, StackView.PushTransition) + } else { + pageStackView.push(p_obj, {}, StackView.Immediate) + } + +// if (reset) { +// p_obj.logic.onUpdatePage(); +// } + + p_obj.activated(reset) + } + + function closePage() { + if (pageStackView.depth <= 1) { + return + } + pageStackView.currentItem.deactivated() + pageStackView.pop() + } + + function setStartPage(page, slide) { + if (pageStackView.depth > 0) { + pageStackView.currentItem.deactivated() + } + + pageStackView.clear() + if (slide) { + pageStackView.push(pages[page], {}, StackView.PushTransition) + } else { + pageStackView.push(pages[page], {}, StackView.Immediate) + } + if (page === PageEnum.Start) { + UiLogic.pushButtonBackFromStartVisible = !pageStackView.empty + UiLogic.onUpdatePage(); + } + } + + Rectangle { + anchors.fill: parent + color: "#0E0E11" + } + + StackView { + id: pageStackView + anchors.fill: parent + focus: true + + onCurrentItemChanged: function() { + UiLogic.currentPageValue = currentItem.page + } + + onDepthChanged: function() { + UiLogic.pagesStackDepth = depth + } + + Keys.onPressed: function(event) { + UiLogic.keyPressEvent(event.key) + event.accepted = true + } + } + + Connections { + target: UiLogic + function onGoToPage(page, reset, slide) { + root.gotoPage(PageType.Basic, page, reset, slide) + } + + function onGoToProtocolPage(protocol, reset, slide) { + root.gotoPage(PageType.Proto, protocol, reset, slide) + } + + function onGoToShareProtocolPage(protocol, reset, slide) { + root.gotoPage(PageType.ShareProto, protocol, reset, slide) + } + + function onClosePage() { + root.closePage() + } + + function onSetStartPage(page, slide) { + root.setStartPage(page, slide) + } + + function onShow() { + root.show() + } + + function onHide() { + root.hide() + } + + function onRaise() { + root.show() + root.raise() + root.requestActivate() + } + } + + PageLoader { + id: pageLoader + + onFinished: { + UiLogic.initalizeUiLogic() + } + } + +} From 3d63d6c0f2c64c62b40bc53622b796b851588d2a Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 14 Apr 2023 19:31:10 +0300 Subject: [PATCH 006/278] added PageSetupWizardCredentials and PageSetupWizardProtocols - fixed hover and pressed effects for controls --- client/resources.qrc | 4 +- client/ui/pages.h | 4 +- client/ui/qml/Controls2/CardType.qml | 38 ++++--- client/ui/qml/Controls2/CheckBoxType.qml | 4 +- client/ui/qml/Controls2/HeaderTextType.qml | 66 ++++++++---- client/ui/qml/Controls2/ImageButtonType.qml | 4 +- .../ui/qml/Controls2/LabelWithButtonType.qml | 44 ++++++-- .../qml/Controls2/TextFieldWithHeaderType.qml | 8 +- ...als.qml => PageSetupWizardCredentials.qml} | 24 +++-- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 74 +++++++++++++ .../qml/Pages2/PageSetupWizardProtocols.qml | 102 ++++++++++++++++++ client/ui/qml/Pages2/PageStart.qml | 10 +- 12 files changed, 314 insertions(+), 68 deletions(-) rename client/ui/qml/Pages2/{PageCredentials.qml => PageSetupWizardCredentials.qml} (78%) create mode 100644 client/ui/qml/Pages2/PageSetupWizardEasy.qml create mode 100644 client/ui/qml/Pages2/PageSetupWizardProtocols.qml diff --git a/client/resources.qrc b/client/resources.qrc index f95ef585..f14c28ae 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -185,8 +185,10 @@ ui/qml/Controls2/BodyTextType.qml ui/qml/Controls2/FlickableType.qml ui/qml/Controls2/Header2TextType.qml - ui/qml/Pages2/PageCredentials.qml + ui/qml/Pages2/PageSetupWizardCredentials.qml ui/qml/Controls2/HeaderTextType.qml images/controls/arrow-left.svg + ui/qml/Pages2/PageSetupWizardProtocols.qml + ui/qml/Pages2/PageSetupWizardEasy.qml diff --git a/client/ui/pages.h b/client/ui/pages.h index c33289bb..4aba79f2 100644 --- a/client/ui/pages.h +++ b/client/ui/pages.h @@ -24,7 +24,9 @@ enum class Page {Start = 0, NewServer, NewServerProtocols, Vpn, Wizard, WizardLow, WizardMedium, WizardHigh, WizardVpnMode, ServerConfiguringProgress, GeneralSettings, AppSettings, NetworkSettings, ServerSettings, ServerContainers, ServersList, ShareConnection, Sites, - ProtocolSettings, ProtocolShare, QrDecoder, QrDecoderIos, About, ViewConfig, AdvancedServerSettings, Test, Credentials}; + ProtocolSettings, ProtocolShare, QrDecoder, QrDecoderIos, About, ViewConfig, AdvancedServerSettings, + + Test, WizardCredentials, WizardProtocols, WizardEasySetup}; Q_ENUM_NS(Page) static void declareQmlPageEnum() { diff --git a/client/ui/qml/Controls2/CardType.qml b/client/ui/qml/Controls2/CardType.qml index 31b51f31..fa7ee514 100644 --- a/client/ui/qml/Controls2/CardType.qml +++ b/client/ui/qml/Controls2/CardType.qml @@ -9,16 +9,17 @@ RadioButton { property string bodyText property string footerText - property string hoveredColor: Qt.rgba(255, 255, 255, 0.05) - property string defaultColor: Qt.rgba(255, 255, 255, 0) - property string disabledColor: Qt.rgba(255, 255, 255, 0) - property string pressedColor: Qt.rgba(255, 255, 255, 0.05) + property string hoveredColor: Qt.rgba(1, 1, 1, 0.05) + property string defaultColor: Qt.rgba(1, 1, 1, 0) + property string disabledColor: Qt.rgba(1, 1, 1, 0) + property string pressedColor: Qt.rgba(1, 1, 1, 0.05) + property string selectedColor: Qt.rgba(1, 1, 1, 0) property string textColor: "#0E0E11" - property string pressedBorderColor: Qt.rgba(251, 178, 106, 0.3) - property string hoveredBorderColor: "transparent" - property string defaultBodredColor: "#FBB26A" + property string pressedBorderColor: Qt.rgba(251/255, 178/255, 106/255, 0.3) + property string selectedBorderColor: "#FBB26A" + property string defaultBodredColor: "transparent" property int borderWidth: 0 implicitWidth: content.implicitWidth @@ -32,30 +33,34 @@ RadioButton { color: { if (root.enabled) { - if(root.checked) { - return pressedColor + if (root.hovered) { + return hoveredColor + } else if (root.checked) { + return selectedColor } - return hovered ? hoveredColor : defaultColor + return defaultColor } else { return disabledColor } } + border.color: { if (root.enabled) { - if(root.checked) { + if (root.pressed) { return pressedBorderColor + } else if (root.checked) { + return selectedBorderColor } - return hovered ? hoveredBorderColor : defaultBodredColor - } else { - return defaultBodredColor } + return defaultBodredColor } + border.width: { if (root.enabled) { if(root.checked) { return 1 } - return hovered ? 0 : 1 + return root.pressed ? 1 : 0 } else { return 0 } @@ -64,6 +69,9 @@ RadioButton { Behavior on color { PropertyAnimation { duration: 200 } } + Behavior on border.color { + PropertyAnimation { duration: 200 } + } } ColumnLayout { diff --git a/client/ui/qml/Controls2/CheckBoxType.qml b/client/ui/qml/Controls2/CheckBoxType.qml index c547a8e4..a432f7b8 100644 --- a/client/ui/qml/Controls2/CheckBoxType.qml +++ b/client/ui/qml/Controls2/CheckBoxType.qml @@ -6,9 +6,9 @@ import Qt5Compat.GraphicalEffects Item { id: root - property string hoveredColor: Qt.rgba(255, 255, 255, 0.05) + property string hoveredColor: Qt.rgba(1, 1, 1, 0.05) property string defaultColor: "transparent" - property string pressedColor: Qt.rgba(255, 255, 255, 0.05) + property string pressedColor: Qt.rgba(1, 1, 1, 0.05) property string defaultBorderColor: "#D7D8DB" property string checkedBorderColor: "#FBB26A" diff --git a/client/ui/qml/Controls2/HeaderTextType.qml b/client/ui/qml/Controls2/HeaderTextType.qml index 60fa4e95..b60ccce4 100644 --- a/client/ui/qml/Controls2/HeaderTextType.qml +++ b/client/ui/qml/Controls2/HeaderTextType.qml @@ -1,40 +1,64 @@ import QtQuick import QtQuick.Layouts -ColumnLayout { +Item { id: root property string buttonImage property string headerText - property var wrapMode + property string descriptionText - ImageButtonType { - id: button + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight - Layout.leftMargin: -6 + ColumnLayout { + id: content + anchors.fill: parent - hoverEnabled: false - image: root.buttonImage - onClicked: { - if (onClickedFunc && typeof onClickedFunc === "function") { - onClickedFunc() + ImageButtonType { + id: backButton + + Layout.leftMargin: -6 + + image: root.buttonImage + imageColor: "#D7D8DB" + onClicked: { + UiLogic.closePage() } } - } - Text { - id: header + Text { + id: header - text: root.headerText + text: root.headerText - color: "#D7D8DB" - font.pixelSize: 36 - font.weight: 700 - font.family: "PT Root UI VF" - font.letterSpacing: -0.03 + color: "#D7D8DB" + font.pixelSize: 36 + font.weight: 700 + font.family: "PT Root UI VF" + font.letterSpacing: -0.03 - wrapMode: Text.WordWrap + wrapMode: Text.WordWrap - height: 38 + height: 38 + Layout.fillWidth: true + } + + Text { + id: description + + text: root.descriptionText + + color: "#878B91" + font.pixelSize: 16 + font.weight: 400 + font.family: "PT Root UI VF" + font.letterSpacing: -0.03 + + wrapMode: Text.WordWrap + + height: 24 + Layout.fillWidth: true + } } } diff --git a/client/ui/qml/Controls2/ImageButtonType.qml b/client/ui/qml/Controls2/ImageButtonType.qml index b6262906..72c78342 100644 --- a/client/ui/qml/Controls2/ImageButtonType.qml +++ b/client/ui/qml/Controls2/ImageButtonType.qml @@ -7,9 +7,9 @@ Button { property string image - property string hoveredColor: Qt.rgba(255, 255, 255, 0.08) + property string hoveredColor: Qt.rgba(1, 1, 1, 0.08) property string defaultColor: "transparent" - property string pressedColor: Qt.rgba(255, 255, 255, 0.12) + property string pressedColor: Qt.rgba(1, 1, 1, 0.12) property string imageColor: "#878B91" diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml index 5da4db5f..60c48569 100644 --- a/client/ui/qml/Controls2/LabelWithButtonType.qml +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -6,24 +6,48 @@ Item { id: root property string text + property string descriptionText property var onClickedFunc property alias buttonImage : button.image - implicitWidth: 360 - implicitHeight: 72 + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight RowLayout { + id: content anchors.fill: parent - Text { - font.family: "PT Root UI" - font.styleName: "normal" - font.pixelSize: 18 - color: "#d7d8db" - text: root.text - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter + ColumnLayout { + Text { + font.family: "PT Root UI" + font.styleName: "normal" + font.pixelSize: 18 + color: "#d7d8db" + text: root.text + + Layout.fillWidth: true + height: 22 + + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + } + + Text { + font.family: "PT Root UI" + font.styleName: "normal" + font.pixelSize: 13 + font.letterSpacing: 0.02 + color: "#878B91" + text: root.descriptionText + wrapMode: Text.WordWrap + + Layout.fillWidth: true + height: 16 + + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + } } ImageButtonType { diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml index dd0d2907..350306bb 100644 --- a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -18,8 +18,12 @@ Item { anchors.fill: parent color: "#1c1d21" radius: 16 - border.color: "#d7d8db" - border.width: textField.focus ? 1 : 0 + border.color: textField.focus ? "#d7d8db" : "#2C2D30" + border.width: 1 + + Behavior on border.color { + PropertyAnimation { duration: 200 } + } } ColumnLayout { diff --git a/client/ui/qml/Pages2/PageCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml similarity index 78% rename from client/ui/qml/Pages2/PageCredentials.qml rename to client/ui/qml/Pages2/PageSetupWizardCredentials.qml index 509ee6a6..65c390ab 100644 --- a/client/ui/qml/Pages2/PageCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -11,7 +11,7 @@ import "../Config" PageBase { id: root - page: PageEnum.Credentials + page: PageEnum.WizardCredentials FlickableType { id: fl @@ -32,15 +32,11 @@ PageBase { HeaderTextType { Layout.fillWidth: true - Layout.bottomMargin: 16 - Layout.topMargin: 66 - - Layout.preferredWidth: 328 + Layout.topMargin: 20 buttonImage: "qrc:/images/controls/arrow-left.svg" headerText: "Подключение к серверу" - wrapMode: Text.WordWrap } TextFieldWithHeaderType { @@ -60,23 +56,31 @@ PageBase { BasicButtonType { Layout.fillWidth: true - Layout.topMargin: 40 + Layout.topMargin: 24 text: qsTr("Настроить сервер простым образом") + + onClicked: function() { + UiLogic.goToPage(PageEnum.WizardEasySetup) + } } BasicButtonType { Layout.fillWidth: true - Layout.topMargin: 8 + Layout.topMargin: -8 defaultColor: "transparent" - hoveredColor: Qt.rgba(255, 255, 255, 0.08) - pressedColor: Qt.rgba(255, 255, 255, 0.12) + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) disabledColor: "#878B91" textColor: "#D7D8DB" borderWidth: 1 text: qsTr("Выбрать протокол для установки") + + onClicked: function() { + UiLogic.goToPage(PageEnum.WizardProtocols) + } } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml new file mode 100644 index 00000000..80962974 --- /dev/null +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -0,0 +1,74 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "./" +import "../Pages" +import "../Controls2" +import "../Config" + +PageBase { + id: root + page: PageEnum.WizardEasySetup + + FlickableType { + id: fl + 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 + + spacing: 16 + + HeaderTextType { + Layout.fillWidth: true + Layout.topMargin: 20 + + buttonImage: "qrc:/images/controls/arrow-left.svg" + + headerText: "Какой уровень контроля интернета в вашем регионе?" + } + + CardType { + Layout.fillWidth: true + + headerText: "Высокий" + bodyText: "Многие иностранные сайты и VPN-провайдеры заблокированы" + } + + CardType { + Layout.fillWidth: true + + checked: true + + headerText: "Средний" + bodyText: "Некоторые иностранные сайты заблокированы, но VPN-провайдеры не блокируются" + } + + CardType { + Layout.fillWidth: true + + headerText: "Низкий" + bodyText: "Хочу просто повысить уровень приватности" + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.bottomMargin: 32 + + text: qsTr("Продолжить") + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml new file mode 100644 index 00000000..b2fb7d57 --- /dev/null +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -0,0 +1,102 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 + +import "./" +import "../Pages" +import "../Controls2" +import "../Config" + +PageBase { + id: root + page: PageEnum.WizardProtocols + + SortFilterProxyModel { + id: containersModel + sourceModel: UiLogic.containersModel + filters: [ + ValueFilter { + roleName: "is_installed_role" + value: false + }, + ValueFilter { + roleName: "service_type_role" + value: ProtocolEnum.Vpn + } + ] + } + + FlickableType { + id: fl + anchors.top: root.top + anchors.bottom: root.bottom + contentHeight: content.height + + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + anchors.topMargin: 20 + + spacing: 16 + + HeaderTextType { + width: parent.width + buttonImage: "qrc:/images/controls/arrow-left.svg" + + headerText: "Протокол подключения" + descriptionText: "Выберите более приоритетный для вас. Позже можно будет установить остальные протоколы и доп сервисы, вроде DNS-прокси и SFTP." + } + + ListView { + id: containers + width: parent.width + height: containers.contentItem.height + currentIndex: -1 + clip: true + interactive: false + model: containersModel + + delegate: Item { + implicitWidth: containers.width + implicitHeight: delegateContent.implicitHeight + + ColumnLayout { + id: delegateContent + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + LabelWithButtonType { + id: container + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.bottomMargin: 16 + + text: name_role + descriptionText: desc_role + buttonImage: "qrc:/images/controls/chevron-right.svg" + + } + + Rectangle { + Layout.fillWidth: true + height: 1 + color: "#2C2D30" + } + } + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 6f8f0356..5d303cb8 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -31,7 +31,7 @@ PageBase { source: "qrc:/images/amneziaBigLogo.png" Layout.alignment: Qt.AlignCenter - Layout.topMargin: 80 + Layout.topMargin: 32 Layout.leftMargin: 8 Layout.rightMargin: 8 Layout.preferredWidth: 344 @@ -67,8 +67,8 @@ PageBase { Layout.rightMargin: 16 defaultColor: "transparent" - hoveredColor: Qt.rgba(255, 255, 255, 0.08) - pressedColor: Qt.rgba(255, 255, 255, 0.12) + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) disabledColor: "#878B91" textColor: "#D7D8DB" borderWidth: 1 @@ -123,6 +123,7 @@ PageBase { Layout.alignment: Qt.AlignHCenter text: "Данные для подключения" + wrapMode: Text.WordWrap } LabelWithButtonType { @@ -134,7 +135,8 @@ PageBase { buttonImage: "qrc:/images/controls/chevron-right.svg" onClickedFunc: function() { - UiLogic.goToPage(PageEnum.Credentials) + UiLogic.goToPage(PageEnum.WizardCredentials) + drawer.visible = false } } Rectangle { From a9ebf534c691e86536b652359e5bb8ab11f3f0d6 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 25 Apr 2023 08:04:20 +0300 Subject: [PATCH 007/278] added DropDown component --- client/images/controls/chevron-down.svg | 3 + client/images/controls/chevron-up.svg | 3 + client/resources.qrc | 2 + client/ui/pages.h | 2 +- client/ui/qml/Controls2/CardType.qml | 1 - client/ui/qml/Controls2/DropDownType.qml | 222 ++++++++++++++++++ client/ui/qml/Controls2/FlickableType.qml | 4 +- .../ui/qml/Controls2/LabelWithButtonType.qml | 4 +- client/ui/qml/Pages2/PageStart.qml | 35 ++- client/ui/qml/main2.qml | 2 +- 10 files changed, 261 insertions(+), 17 deletions(-) create mode 100644 client/images/controls/chevron-down.svg create mode 100644 client/images/controls/chevron-up.svg diff --git a/client/images/controls/chevron-down.svg b/client/images/controls/chevron-down.svg new file mode 100644 index 00000000..3f453815 --- /dev/null +++ b/client/images/controls/chevron-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/images/controls/chevron-up.svg b/client/images/controls/chevron-up.svg new file mode 100644 index 00000000..b51628d2 --- /dev/null +++ b/client/images/controls/chevron-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/resources.qrc b/client/resources.qrc index d8cd40c5..3d009107 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -196,5 +196,7 @@ images/controls/arrow-left.svg ui/qml/Pages2/PageSetupWizardProtocols.qml ui/qml/Pages2/PageSetupWizardEasy.qml + images/controls/chevron-down.svg + images/controls/chevron-up.svg diff --git a/client/ui/pages.h b/client/ui/pages.h index 808725d2..9c5db53c 100644 --- a/client/ui/pages.h +++ b/client/ui/pages.h @@ -26,7 +26,7 @@ enum class Page {Start = 0, NewServer, NewServerProtocols, Vpn, GeneralSettings, AppSettings, NetworkSettings, ServerSettings, ServerContainers, ServersList, ShareConnection, Sites, ProtocolSettings, ProtocolShare, QrDecoder, QrDecoderIos, About, ViewConfig, - AdvancedServerSettings, ClientManagement, ClientInfo + AdvancedServerSettings, ClientManagement, ClientInfo, Test, WizardCredentials, WizardProtocols, WizardEasySetup}; Q_ENUM_NS(Page) diff --git a/client/ui/qml/Controls2/CardType.qml b/client/ui/qml/Controls2/CardType.qml index fa7ee514..4b94cb1a 100644 --- a/client/ui/qml/Controls2/CardType.qml +++ b/client/ui/qml/Controls2/CardType.qml @@ -111,7 +111,6 @@ RadioButton { Text { text: root.footerText visible: root.footerText !== "" - enabled: root.footerText !== "" color: "#878B91" font.pixelSize: 13 font.weight: 400 diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 5560aee7..ea02ac3c 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -1,5 +1,227 @@ import QtQuick +import QtQuick.Controls +import QtQuick.Layouts Item { + id: root + property string text + property string descriptionText + + property var onClickedFunc + property string buttonImage: "qrc:/images/controls/chevron-down.svg" + + property string defaultColor: "#1C1D21" + + property string borderColor: "#494B50" + + property alias menuModel: menuContent.model + + width: buttonContent.implicitWidth + height: buttonContent.implicitHeight + + Rectangle { + id: buttonBackground + anchors.fill: buttonContent + + radius: 16 + color: defaultColor + border.color: borderColor + + Behavior on border.width { + PropertyAnimation { duration: 200 } + } + } + + RowLayout { + id: buttonContent + anchors.fill: parent + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + ColumnLayout { + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.topMargin: 16 + Layout.bottomMargin: 16 + + Text { + visible: root.descriptionText !== "" + + font.family: "PT Root UI" + font.styleName: "normal" + font.pixelSize: 13 + font.letterSpacing: 0.02 + color: "#878B91" + text: root.descriptionText + wrapMode: Text.WordWrap + + Layout.fillWidth: true + height: 16 + + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + } + + Text { + font.family: "PT Root UI" + font.styleName: "normal" + font.pixelSize: 16 + color: "#d7d8db" + text: root.text + + Layout.fillWidth: true + height: 24 + + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + } + } + + ImageButtonType { + id: button + + Layout.rightMargin: 16 + + hoverEnabled: false + image: buttonImage + onClicked: { + if (onClickedFunc && typeof onClickedFunc === "function") { + onClickedFunc() + } + } + + Layout.alignment: Qt.AlignRight + } + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + + onEntered: { + buttonBackground.border.width = 1 + } + + onExited: { + buttonBackground.border.width = 0 + } + + onClicked: { + menu.visible = true + } + } + + Drawer { + id: menu + + edge: Qt.BottomEdge + width: parent.width + height: parent.height * 0.8 + + clip: true + modal: true + + background: Rectangle { + anchors.fill: parent + anchors.bottomMargin: -radius + radius: 16 + color: "#1C1D21" + } + + Overlay.modal: Rectangle { + color: Qt.rgba(14/255, 14/255, 17/255, 0.8) + } + + Column { + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 + + spacing: 16 + + Header2TextType { + width: parent.width + + text: "Данные для подключения" + wrapMode: Text.WordWrap + + leftPadding: 16 + rightPadding: 16 + } + + ButtonGroup { + id: radioButtonGroup + } + + ListView { + id: menuContent + width: parent.width + height: menuContent.contentItem.height + + currentIndex: -1 + + clip: true + interactive: false + + delegate: Item { + implicitWidth: menuContent.width + implicitHeight: radioButton.implicitHeight + + RadioButton { + id: radioButton + + implicitWidth: parent.width + implicitHeight: radioButtonContent.implicitHeight + + hoverEnabled: true + + ButtonGroup.group: radioButtonGroup + + indicator: Rectangle { + anchors.fill: parent + color: radioButton.hovered ? "#2C2D30" : "#1C1D21" + } + + RowLayout { + id: radioButtonContent + anchors.fill: parent + + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + z: 1 + + Text { + id: text + + text: modelData + color: "#D7D8DB" + font.pixelSize: 16 + font.weight: 400 + font.family: "PT Root UI VF" + + height: 24 + + Layout.fillWidth: true + Layout.topMargin: 20 + Layout.bottomMargin: 20 + } + + Image { + source: "qrc:/images/controls/check.svg" + visible: radioButton.checked + width: 24 + height: 24 + + Layout.rightMargin: 8 + } + } + } + } + } + } + } } diff --git a/client/ui/qml/Controls2/FlickableType.qml b/client/ui/qml/Controls2/FlickableType.qml index b7c1203f..073be058 100644 --- a/client/ui/qml/Controls2/FlickableType.qml +++ b/client/ui/qml/Controls2/FlickableType.qml @@ -9,8 +9,8 @@ Flickable { width: parent.width anchors.bottom: parent.bottom - anchors.left: root.left - anchors.right: root.right + anchors.left: parent.left + anchors.right: parent.right anchors.rightMargin: 1 Keys.onUpPressed: scrollBar.decrease() diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml index 60c48569..92606940 100644 --- a/client/ui/qml/Controls2/LabelWithButtonType.qml +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -9,7 +9,7 @@ Item { property string descriptionText property var onClickedFunc - property alias buttonImage : button.image + property alias buttonImage: button.image implicitWidth: content.implicitWidth implicitHeight: content.implicitHeight @@ -42,6 +42,8 @@ Item { text: root.descriptionText wrapMode: Text.WordWrap + visible: root.descriptionText !== "" + Layout.fillWidth: true height: 16 diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 5d303cb8..f87e0c6e 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -79,18 +79,37 @@ PageBase { // UiLogic.goToPage(PageEnum.Start) // } } + + DropDownType { + Layout.fillWidth: true + + text: "IP, логин и пароль от сервера" + descriptionText: "IP, логин и пароль от сервера" + + menuModel: [ + qsTr("SHA512"), + qsTr("SHA384"), + qsTr("SHA256"), + qsTr("SHA3-512"), + qsTr("SHA3-384"), + qsTr("SHA3-256"), + qsTr("whirlpool"), + qsTr("BLAKE2b512"), + qsTr("BLAKE2s256"), + qsTr("SHA1") + ] + } } Drawer { id: drawer - y: 0 - x: 0 edge: Qt.BottomEdge width: parent.width height: parent.height * 0.4375 clip: true + modal: true background: Rectangle { anchors.fill: parent @@ -99,15 +118,9 @@ PageBase { color: "#1C1D21" } - modal: true - //interactive: activeFocus - -// onAboutToHide: { -// pageLoader.focus = true -// } -// onAboutToShow: { -// tfSshLog.focus = true -// } + Overlay.modal: Rectangle { + color: Qt.rgba(14/255, 14/255, 17/255, 0.8) + } ColumnLayout { anchors.top: parent.top diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index deb64e89..c7e06eaa 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -135,7 +135,7 @@ Window { id: pageLoader onFinished: { - UiLogic.initalizeUiLogic() + UiLogic.initializeUiLogic() } } From 904e173037b300274aca344a45952a93e349111f Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 26 Apr 2023 08:30:02 +0300 Subject: [PATCH 008/278] added HorizontalRadioButton and VerticalRadioButton components --- client/resources.qrc | 8 +- client/ui/qml/Controls2/CheckBoxType.qml | 8 +- client/ui/qml/Controls2/DropDownType.qml | 2 + .../{HeaderTextType.qml => HeaderType.qml} | 0 .../qml/Controls2/HorizontalRadioButton.qml | 95 +++++++++ .../{ => TextTypes}/BodyTextType.qml | 0 .../{ => TextTypes}/Header2TextType.qml | 0 .../ui/qml/Controls2/VerticalRadioButton.qml | 180 ++++++++++++++++++ .../qml/Pages2/PageSetupWizardCredentials.qml | 2 +- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 2 +- .../qml/Pages2/PageSetupWizardProtocols.qml | 2 +- client/ui/qml/Pages2/PageStart.qml | 39 ++++ 12 files changed, 330 insertions(+), 8 deletions(-) rename client/ui/qml/Controls2/{HeaderTextType.qml => HeaderType.qml} (100%) create mode 100644 client/ui/qml/Controls2/HorizontalRadioButton.qml rename client/ui/qml/Controls2/{ => TextTypes}/BodyTextType.qml (100%) rename client/ui/qml/Controls2/{ => TextTypes}/Header2TextType.qml (100%) create mode 100644 client/ui/qml/Controls2/VerticalRadioButton.qml diff --git a/client/resources.qrc b/client/resources.qrc index 3d009107..5fe682a9 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -188,15 +188,17 @@ ui/qml/PageLoader.qml images/amneziaBigLogo.png images/amneziaBigLogo.svg - ui/qml/Controls2/BodyTextType.qml ui/qml/Controls2/FlickableType.qml - ui/qml/Controls2/Header2TextType.qml ui/qml/Pages2/PageSetupWizardCredentials.qml - ui/qml/Controls2/HeaderTextType.qml + ui/qml/Controls2/HeaderType.qml images/controls/arrow-left.svg ui/qml/Pages2/PageSetupWizardProtocols.qml ui/qml/Pages2/PageSetupWizardEasy.qml images/controls/chevron-down.svg images/controls/chevron-up.svg + ui/qml/Controls2/TextTypes/BodyTextType.qml + ui/qml/Controls2/TextTypes/Header2TextType.qml + ui/qml/Controls2/HorizontalRadioButton.qml + ui/qml/Controls2/VerticalRadioButton.qml diff --git a/client/ui/qml/Controls2/CheckBoxType.qml b/client/ui/qml/Controls2/CheckBoxType.qml index a432f7b8..92357c52 100644 --- a/client/ui/qml/Controls2/CheckBoxType.qml +++ b/client/ui/qml/Controls2/CheckBoxType.qml @@ -6,6 +6,9 @@ import Qt5Compat.GraphicalEffects Item { id: root + property string text + property string descriptionText + property string hoveredColor: Qt.rgba(1, 1, 1, 0.05) property string defaultColor: "transparent" property string pressedColor: Qt.rgba(1, 1, 1, 0.05) @@ -71,7 +74,7 @@ Item { ColumnLayout { Text { - text: "Paragraph" + text: root.text color: "#D7D8DB" font.pixelSize: 18 font.weight: 400 @@ -82,7 +85,7 @@ Item { } Text { - text: "Caption" + text: root.descriptionText color: "#878b91" font.pixelSize: 13 font.weight: 400 @@ -94,6 +97,7 @@ Item { } } } + MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index ea02ac3c..8abb03d2 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -2,6 +2,8 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import "TextTypes" + Item { id: root diff --git a/client/ui/qml/Controls2/HeaderTextType.qml b/client/ui/qml/Controls2/HeaderType.qml similarity index 100% rename from client/ui/qml/Controls2/HeaderTextType.qml rename to client/ui/qml/Controls2/HeaderType.qml diff --git a/client/ui/qml/Controls2/HorizontalRadioButton.qml b/client/ui/qml/Controls2/HorizontalRadioButton.qml new file mode 100644 index 00000000..6f00d210 --- /dev/null +++ b/client/ui/qml/Controls2/HorizontalRadioButton.qml @@ -0,0 +1,95 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +RadioButton { + id: root + + property string hoveredColor: Qt.rgba(1, 1, 1, 0.05) + property string defaultColor: Qt.rgba(1, 1, 1, 0) + property string disabledColor: Qt.rgba(1, 1, 1, 0) + property string selectedColor: Qt.rgba(1, 1, 1, 0) + + property string textColor: "#0E0E11" + + property string pressedBorderColor: "#494B50" + property string selectedBorderColor: "#FBB26A" + property string defaultBodredColor: "transparent" + property int borderWidth: 0 + + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight + + hoverEnabled: true + + indicator: Rectangle { + anchors.fill: parent + radius: 16 + + color: { + if (root.enabled) { + if (root.hovered) { + return hoveredColor + } else if (root.checked) { + return selectedColor + } + return defaultColor + } else { + return disabledColor + } + } + + border.color: { + if (root.enabled) { + if (root.pressed) { + return pressedBorderColor + } else if (root.checked) { + return selectedBorderColor + } + } + return defaultBodredColor + } + + border.width: { + if (root.enabled) { + if(root.checked) { + return 1 + } + return root.pressed ? 1 : 0 + } else { + return 0 + } + } + + Behavior on color { + PropertyAnimation { duration: 200 } + } + Behavior on border.color { + PropertyAnimation { duration: 200 } + } + } + + ColumnLayout { + id: content + anchors.fill: parent + spacing: 16 + + Text { + text: root.text + wrapMode: Text.WordWrap + color: "#D7D8DB" + font.pixelSize: 16 + font.weight: 400 + font.family: "PT Root UI VF" + + height: 24 + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.topMargin: 16 + Layout.bottomMargin: 16 + + horizontalAlignment: Qt.AlignHCenter + } + } +} diff --git a/client/ui/qml/Controls2/BodyTextType.qml b/client/ui/qml/Controls2/TextTypes/BodyTextType.qml similarity index 100% rename from client/ui/qml/Controls2/BodyTextType.qml rename to client/ui/qml/Controls2/TextTypes/BodyTextType.qml diff --git a/client/ui/qml/Controls2/Header2TextType.qml b/client/ui/qml/Controls2/TextTypes/Header2TextType.qml similarity index 100% rename from client/ui/qml/Controls2/Header2TextType.qml rename to client/ui/qml/Controls2/TextTypes/Header2TextType.qml diff --git a/client/ui/qml/Controls2/VerticalRadioButton.qml b/client/ui/qml/Controls2/VerticalRadioButton.qml new file mode 100644 index 00000000..e6c374f7 --- /dev/null +++ b/client/ui/qml/Controls2/VerticalRadioButton.qml @@ -0,0 +1,180 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Qt5Compat.GraphicalEffects + +RadioButton { + id: root + + property string descriptionText + + property string hoveredColor: Qt.rgba(1, 1, 1, 0.05) + property string defaultColor: Qt.rgba(1, 1, 1, 0) + property string disabledColor: Qt.rgba(1, 1, 1, 0) + property string selectedColor: Qt.rgba(1, 1, 1, 0) + + property string textColor: "#0E0E11" + + property string pressedBorderColor: Qt.rgba(251/255, 178/255, 106/255, 0.3) + property string selectedBorderColor: "#FBB26A" + property string defaultBodredColor: "transparent" + 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" + + hoverEnabled: true + + indicator: Rectangle { + implicitWidth: 56 + implicitHeight: 56 + radius: 16 + + color: { + if (root.enabled) { + if (root.hovered || contentMouseArea.entered) { + return hoveredColor + } else if (root.checked) { + return selectedColor + } + return defaultColor + } else { + return disabledColor + } + } + + Behavior on color { + PropertyAnimation { duration: 200 } + } + + Rectangle { + width: 24 + height: 24 + radius: 16 + + anchors.centerIn: parent + + color: "transparent" + border.color: { + if (root.enabled) { + if (root.pressed) { + return pressedCircleBorderColor + } else if (root.checked) { + return selectedCircleBorderColor + } + } + return defaultCircleBorderColor + } + + border.width: 1 + + Behavior on border.color { + PropertyAnimation { duration: 200 } + } + + Rectangle { + id: innerCircle + + width: 12 + height: 12 + radius: 16 + + anchors.centerIn: parent + + color: "transparent" + border.color: defaultInnerCircleColor + border.width: { + if (root.enabled) { + if(root.checked) { + return 6 + } + return root.pressed ? 6 : 0 + } else { + return 0 + } + } + + Behavior on border.width { + PropertyAnimation { duration: 200 } + } + } + + DropShadow { + anchors.fill: innerCircle + horizontalOffset: 0 + verticalOffset: 0 + radius: 12 + samples: 13 + color: "#FBB26A" + source: innerCircle + } + } + } + + contentItem: ColumnLayout { + id: content + anchors.left: indicator.right + anchors.top: parent.top + anchors.leftMargin: 8 + spacing: 16 + + Text { + text: root.text + wrapMode: Text.WordWrap + color: "#D7D8DB" + font.pixelSize: 16 + font.weight: 400 + font.family: "PT Root UI VF" + + height: 24 + Layout.fillWidth: true + } + + Text { + font.family: "PT Root UI" + font.styleName: "normal" + font.pixelSize: 13 + font.letterSpacing: 0.02 + color: "#878B91" + text: root.descriptionText + wrapMode: Text.WordWrap + + visible: root.descriptionText !== "" + + Layout.fillWidth: true + height: 16 + } + } + + MouseArea { + id: contentMouseArea + + anchors.fill: content + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + +// onEntered: { +// checkBoxBackground.color = hoveredColor +// } + +// onExited: { +// checkBoxBackground.color = defaultColor +// } + +// onPressedChanged: { +// indicator.source = pressed ? imageSource : "" +// imageColor.color = pressed ? hoveredImageColor : defaultImageColor +// checkBoxBackground.color = pressed ? pressedColor : entered ? hoveredColor : defaultColor +// } + +// onClicked: { +// checkBox.checked = !checkBox.checked +// indicator.source = checkBox.checked ? imageSource : "" +// imageColor.color = checkBox.checked ? checkedImageColor : defaultImageColor +// imageBorder.border.color = checkBox.checked ? checkedBorderColor : defaultBorderColor +// } + } +} diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index 65c390ab..20472de2 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -30,7 +30,7 @@ PageBase { spacing: 16 - HeaderTextType { + HeaderType { Layout.fillWidth: true Layout.topMargin: 20 diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index 80962974..b4121592 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -30,7 +30,7 @@ PageBase { spacing: 16 - HeaderTextType { + HeaderType { Layout.fillWidth: true Layout.topMargin: 20 diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index b2fb7d57..a50da64b 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -49,7 +49,7 @@ PageBase { spacing: 16 - HeaderTextType { + HeaderType { width: parent.width buttonImage: "qrc:/images/controls/arrow-left.svg" diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index f87e0c6e..0cac1b82 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -8,6 +8,7 @@ import "./" import "../Pages" import "../Controls2" import "../Config" +import "../Controls2/TextTypes" PageBase { id: root @@ -99,6 +100,44 @@ PageBase { qsTr("SHA1") ] } + CheckBoxType { +// text: qsTr("Auto-negotiate encryption") + } + CheckBoxType { + text: qsTr("Auto-negotiate encryption") + descriptionText: "dssaa" + } + + Rectangle { + implicitWidth: buttonGroup.implicitWidth + implicitHeight: buttonGroup.implicitHeight + + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + color: "#1C1D21" + radius: 16 + RowLayout { + id: buttonGroup + + spacing: 0 + HorizontalRadioButton { + implicitWidth: (root.width - 32) / 2 + text: "ddsasdasd" + } + HorizontalRadioButton { + implicitWidth: (root.width - 32) / 2 + text: "ddsasdasd" + } + } + } + + VerticalRadioButton { + text: "dsasd" + } + VerticalRadioButton { + text: "dsasd" + } } Drawer { From cfc17cf290d89f004874518211d2d0f0b0d6937a Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 26 Apr 2023 18:37:56 +0300 Subject: [PATCH 009/278] added mousearea to VerticalRadioButton --- client/ui/qml/Controls2/CheckBoxType.qml | 7 +-- .../ui/qml/Controls2/VerticalRadioButton.qml | 50 +++++++------------ client/ui/qml/Pages2/PageStart.qml | 7 +++ 3 files changed, 30 insertions(+), 34 deletions(-) diff --git a/client/ui/qml/Controls2/CheckBoxType.qml b/client/ui/qml/Controls2/CheckBoxType.qml index 92357c52..98f6c5fe 100644 --- a/client/ui/qml/Controls2/CheckBoxType.qml +++ b/client/ui/qml/Controls2/CheckBoxType.qml @@ -17,7 +17,7 @@ Item { property string checkedBorderColor: "#FBB26A" property string checkedImageColor: "#FBB26A" - property string hoveredImageColor: "#A85809" + property string pressedImageColor: "#A85809" property string defaultImageColor: "transparent" property string imageSource: "qrc:/images/controls/check.svg" @@ -40,6 +40,7 @@ Item { id: indicator anchors.verticalCenter: checkBox.verticalCenter anchors.horizontalCenter: checkBox.horizontalCenter + ColorOverlay { id: imageColor anchors.fill: indicator @@ -112,8 +113,8 @@ Item { } onPressedChanged: { - indicator.source = pressed ? imageSource : "" - imageColor.color = pressed ? hoveredImageColor : defaultImageColor + indicator.source = pressed ? imageSource : checkBox.checked ? imageSource : "" + imageColor.color = pressed ? pressedImageColor : checkBox.checked ? checkedImageColor : defaultImageColor checkBoxBackground.color = pressed ? pressedColor : entered ? hoveredColor : defaultColor } diff --git a/client/ui/qml/Controls2/VerticalRadioButton.qml b/client/ui/qml/Controls2/VerticalRadioButton.qml index e6c374f7..058cf9ef 100644 --- a/client/ui/qml/Controls2/VerticalRadioButton.qml +++ b/client/ui/qml/Controls2/VerticalRadioButton.qml @@ -26,16 +26,21 @@ RadioButton { property string defaultInnerCircleColor: "#FBB26A" + implicitWidth: background.implicitWidth + content.implicitWidth + implicitHeight: background.implicitWidth + hoverEnabled: true indicator: Rectangle { + id: background + implicitWidth: 56 implicitHeight: 56 radius: 16 color: { if (root.enabled) { - if (root.hovered || contentMouseArea.entered) { + if (root.hovered) { return hoveredColor } else if (root.checked) { return selectedColor @@ -51,6 +56,8 @@ RadioButton { } Rectangle { + id: outerCircle + width: 24 height: 24 radius: 16 @@ -114,12 +121,14 @@ RadioButton { } } - contentItem: ColumnLayout { + contentItem: Item {} + + ColumnLayout { id: content - anchors.left: indicator.right - anchors.top: parent.top - anchors.leftMargin: 8 - spacing: 16 + anchors.fill: parent + anchors.leftMargin: 8 + background.width + anchors.topMargin: 4 + anchors.bottomMargin: 4 Text { text: root.text @@ -150,31 +159,10 @@ RadioButton { } MouseArea { - id: contentMouseArea - - anchors.fill: content + anchors.fill: root cursorShape: Qt.PointingHandCursor - hoverEnabled: true - -// onEntered: { -// checkBoxBackground.color = hoveredColor -// } - -// onExited: { -// checkBoxBackground.color = defaultColor -// } - -// onPressedChanged: { -// indicator.source = pressed ? imageSource : "" -// imageColor.color = pressed ? hoveredImageColor : defaultImageColor -// checkBoxBackground.color = pressed ? pressedColor : entered ? hoveredColor : defaultColor -// } - -// onClicked: { -// checkBox.checked = !checkBox.checked -// indicator.source = checkBox.checked ? imageSource : "" -// imageColor.color = checkBox.checked ? checkedImageColor : defaultImageColor -// imageBorder.border.color = checkBox.checked ? checkedBorderColor : defaultBorderColor -// } + enabled: false } } + + diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 0cac1b82..331a6aec 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -121,6 +121,7 @@ PageBase { id: buttonGroup spacing: 0 + HorizontalRadioButton { implicitWidth: (root.width - 32) / 2 text: "ddsasdasd" @@ -134,9 +135,15 @@ PageBase { VerticalRadioButton { text: "dsasd" + descriptionText: "asd" + checked: true + + Layout.fillWidth: true } VerticalRadioButton { text: "dsasd" + + Layout.fillWidth: true } } From c7acd63ea7a64d21dd8e0a888ef6a6e4d604b318 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sat, 29 Apr 2023 19:09:16 +0300 Subject: [PATCH 010/278] added SwitcherType and TabButtonType - change CheckBoxType root type - added PageTest --- client/resources.qrc | 4 +- client/ui/qml/Controls2/CheckBoxType.qml | 137 ++++----- client/ui/qml/Controls2/HeaderType.qml | 4 + client/ui/qml/Controls2/SwitcherType.qml | 83 +++++ client/ui/qml/Controls2/TabButtonType.qml | 56 ++++ .../ui/qml/Controls2/VerticalRadioButton.qml | 9 +- client/ui/qml/Pages/PageTest.qml | 125 -------- client/ui/qml/Pages2/PageStart.qml | 69 +---- client/ui/qml/Pages2/PageTest.qml | 285 ++++++++++++++++++ 9 files changed, 492 insertions(+), 280 deletions(-) create mode 100644 client/ui/qml/Controls2/SwitcherType.qml create mode 100644 client/ui/qml/Controls2/TabButtonType.qml delete mode 100644 client/ui/qml/Pages/PageTest.qml create mode 100644 client/ui/qml/Pages2/PageTest.qml diff --git a/client/resources.qrc b/client/resources.qrc index 5fe682a9..1d79af1c 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -173,7 +173,6 @@ server_scripts/check_user_in_sudo.sh ui/qml/Controls2/BasicButtonType.qml ui/qml/Controls2/TextFieldWithHeaderType.qml - ui/qml/Pages/PageTest.qml fonts/pt-root-ui_vf.ttf ui/qml/Controls2/LabelWithButtonType.qml images/controls/arrow-right.svg @@ -200,5 +199,8 @@ ui/qml/Controls2/TextTypes/Header2TextType.qml ui/qml/Controls2/HorizontalRadioButton.qml ui/qml/Controls2/VerticalRadioButton.qml + ui/qml/Controls2/SwitcherType.qml + ui/qml/Pages2/PageTest.qml + ui/qml/Controls2/TabButtonType.qml diff --git a/client/ui/qml/Controls2/CheckBoxType.qml b/client/ui/qml/Controls2/CheckBoxType.qml index 98f6c5fe..1a22f326 100644 --- a/client/ui/qml/Controls2/CheckBoxType.qml +++ b/client/ui/qml/Controls2/CheckBoxType.qml @@ -3,10 +3,9 @@ import QtQuick.Controls import QtQuick.Layouts import Qt5Compat.GraphicalEffects -Item { +CheckBox { id: root - property string text property string descriptionText property string hoveredColor: Qt.rgba(1, 1, 1, 0.05) @@ -22,107 +21,87 @@ Item { property string imageSource: "qrc:/images/controls/check.svg" - implicitWidth: content.implicitWidth - implicitHeight: content.implicitHeight + hoverEnabled: true - RowLayout { - id: content + indicator: Rectangle { + id: checkBoxBackground - anchors.fill: parent + implicitWidth: 56 + implicitHeight: 56 + radius: 16 - CheckBox { - id: checkBox + color: { + if (root.hovered) { + return hoveredColor + } + return defaultColor + } - implicitWidth: 56 - implicitHeight: 56 + Behavior on color { + PropertyAnimation { duration: 200 } + } - indicator: Image { + Rectangle { + id: imageBorder + + anchors.centerIn: parent + width: 24 + height: 24 + color: "transparent" + border.color: root.checked ? checkedBorderColor : defaultBorderColor + border.width: 1 + radius: 4 + + Image { id: indicator - anchors.verticalCenter: checkBox.verticalCenter - anchors.horizontalCenter: checkBox.horizontalCenter + anchors.centerIn: parent + + source: root.pressed ? imageSource : root.checked ? imageSource : "" ColorOverlay { id: imageColor anchors.fill: indicator source: indicator - } - } - Rectangle { - id: imageBorder - - anchors.verticalCenter: checkBox.verticalCenter - anchors.horizontalCenter: checkBox.horizontalCenter - width: 24 - height: 24 - color: "transparent" - border.color: checkBox.checked ? checkedBorderColor : defaultBorderColor - border.width: 1 - radius: 4 - } - - background: Rectangle { - id: checkBoxBackground - radius: 16 - - color: "transparent" - - Behavior on color { - PropertyAnimation { duration: 200 } + color: root.pressed ? pressedImageColor : root.checked ? checkedImageColor : defaultImageColor } } } + } - ColumnLayout { - Text { - text: root.text - color: "#D7D8DB" - font.pixelSize: 18 - font.weight: 400 - font.family: "PT Root UI VF" + contentItem: ColumnLayout { + anchors.fill: parent + anchors.leftMargin: 8 + checkBoxBackground.width - height: 22 - Layout.fillWidth: true - } + Text { + text: root.text + color: "#D7D8DB" + font.pixelSize: 18 + font.weight: 400 + font.family: "PT Root UI VF" - Text { - text: root.descriptionText - color: "#878b91" - font.pixelSize: 13 - font.weight: 400 - font.family: "PT Root UI VF" - font.letterSpacing: 0.02 + height: 22 + Layout.fillWidth: true + } - height: 16 - Layout.fillWidth: true - } + Text { + text: root.descriptionText + color: "#878b91" + font.pixelSize: 13 + font.weight: 400 + font.family: "PT Root UI VF" + font.letterSpacing: 0.02 + + height: 16 + Layout.fillWidth: true } } MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor - hoverEnabled: true - - onEntered: { - checkBoxBackground.color = hoveredColor - } - - onExited: { - checkBoxBackground.color = defaultColor - } - - onPressedChanged: { - indicator.source = pressed ? imageSource : checkBox.checked ? imageSource : "" - imageColor.color = pressed ? pressedImageColor : checkBox.checked ? checkedImageColor : defaultImageColor - checkBoxBackground.color = pressed ? pressedColor : entered ? hoveredColor : defaultColor - } - - onClicked: { - checkBox.checked = !checkBox.checked - indicator.source = checkBox.checked ? imageSource : "" - imageColor.color = checkBox.checked ? checkedImageColor : defaultImageColor - imageBorder.border.color = checkBox.checked ? checkedBorderColor : defaultBorderColor - } + enabled: false } } + + diff --git a/client/ui/qml/Controls2/HeaderType.qml b/client/ui/qml/Controls2/HeaderType.qml index b60ccce4..6e10e75c 100644 --- a/client/ui/qml/Controls2/HeaderType.qml +++ b/client/ui/qml/Controls2/HeaderType.qml @@ -22,6 +22,9 @@ Item { image: root.buttonImage imageColor: "#D7D8DB" + + visible: image ? true : false + onClicked: { UiLogic.closePage() } @@ -58,6 +61,7 @@ Item { wrapMode: Text.WordWrap height: 24 + Layout.topMargin: 16 Layout.fillWidth: true } } diff --git a/client/ui/qml/Controls2/SwitcherType.qml b/client/ui/qml/Controls2/SwitcherType.qml new file mode 100644 index 00000000..b593ece8 --- /dev/null +++ b/client/ui/qml/Controls2/SwitcherType.qml @@ -0,0 +1,83 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Switch { + id: root + + property string checkedIndicatorColor: "#412102" + property string defaultIndicatorColor: "transparent" + property string checkedIndicatorBorderColor: "#412102" + property string defaultIndicatorBorderColor: "#494B50" + + property string checkedInnerCircleColor: "#FBB26A" + property string defaultInnerCircleColor: "#D7D8DB" + + property string hoveredIndicatorBackgroundColor: Qt.rgba(1, 1, 1, 0.08) + property string defaultIndicatorBackgroundColor: "transparent" + + indicator: Rectangle { + implicitWidth: 52 + implicitHeight: 32 + x: content.width - width + radius: 16 + color: root.checked ? checkedIndicatorColor : defaultIndicatorColor + border.color: root.checked ? checkedIndicatorBorderColor : defaultIndicatorBorderColor + + Behavior on color { + PropertyAnimation { duration: 200 } + } + Behavior on border.color { + PropertyAnimation { duration: 200 } + } + + Rectangle { + id: innerCircle + + anchors.verticalCenter: parent.verticalCenter + x: root.checked ? parent.width - width - 4 : 8 + width: root.checked ? 24 : 16 + height: root.checked ? 24 : 16 + radius: 23 + color: root.checked ? checkedInnerCircleColor : defaultInnerCircleColor + + Behavior on x { + PropertyAnimation { duration: 200 } + } + } + + Rectangle { + anchors.centerIn: innerCircle + width: 40 + height: 40 + radius: 23 + color: hovered ? hoveredIndicatorBackgroundColor : defaultIndicatorBackgroundColor + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + } + + contentItem: ColumnLayout { + id: content + + Text { + text: root.text + color: "#D7D8DB" + font.pixelSize: 18 + font.weight: 400 + font.family: "PT Root UI VF" + + height: 22 + Layout.fillWidth: true + Layout.bottomMargin: 16 + } + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + enabled: false + } +} diff --git a/client/ui/qml/Controls2/TabButtonType.qml b/client/ui/qml/Controls2/TabButtonType.qml new file mode 100644 index 00000000..f39edefd --- /dev/null +++ b/client/ui/qml/Controls2/TabButtonType.qml @@ -0,0 +1,56 @@ +import QtQuick +import QtQuick.Controls + +TabButton { + id: root + + property string hoveredColor: "#412102" + property string defaultColor: "#2C2D30" + property string selectedColor: "#FBB26A" + + property string textColor: "#D7D8DB" + + property bool isSelected: false + + implicitHeight: 48 + + hoverEnabled: true + + background: Rectangle { + id: background + + anchors.fill: parent + color: "transparent" + + Rectangle { + width: parent.width + height: 1 + y: parent.height - height + color: { + if(root.isSelected) { + return selectedColor + } + return hovered ? hoveredColor : defaultColor + } + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + } + + contentItem: Text { + anchors.fill: background + height: 24 + + font.family: "PT Root UI" + font.styleName: "normal" + font.weight: 500 + font.pixelSize: 16 + color: textColor + text: root.text + + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } +} diff --git a/client/ui/qml/Controls2/VerticalRadioButton.qml b/client/ui/qml/Controls2/VerticalRadioButton.qml index 058cf9ef..edcd1c29 100644 --- a/client/ui/qml/Controls2/VerticalRadioButton.qml +++ b/client/ui/qml/Controls2/VerticalRadioButton.qml @@ -26,9 +26,6 @@ RadioButton { property string defaultInnerCircleColor: "#FBB26A" - implicitWidth: background.implicitWidth + content.implicitWidth - implicitHeight: background.implicitWidth - hoverEnabled: true indicator: Rectangle { @@ -121,14 +118,10 @@ RadioButton { } } - contentItem: Item {} - - ColumnLayout { + contentItem: ColumnLayout { id: content anchors.fill: parent anchors.leftMargin: 8 + background.width - anchors.topMargin: 4 - anchors.bottomMargin: 4 Text { text: root.text diff --git a/client/ui/qml/Pages/PageTest.qml b/client/ui/qml/Pages/PageTest.qml deleted file mode 100644 index 4c571ae0..00000000 --- a/client/ui/qml/Pages/PageTest.qml +++ /dev/null @@ -1,125 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Controls2" -import "../Config" - -PageBase { - id: root - page: PageEnum.Test - logic: ViewConfigLogic - - Rectangle { - anchors.fill: parent - color: "#0E0E11" - } - - FlickableType { - id: fl - anchors.top: root.top - anchors.bottom: root.bottom - contentHeight: content.height - - ColumnLayout { - id: content - enabled: true - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - BasicButtonType { - Layout.fillWidth: true - Layout.topMargin: 10 - text: qsTr("Forget this server") - -// onClicked: { -// UiLogic.goToPage(PageEnum.Start) -// } - } - - BasicButtonType { - Layout.fillWidth: true - Layout.topMargin: 10 - - defaultColor: "transparent" - hoveredColor: Qt.rgba(255, 255, 255, 0.08) - pressedColor: Qt.rgba(255, 255, 255, 0.12) - disabledColor: "#878B91" - textColor: "#D7D8DB" - borderWidth: 1 - - text: qsTr("Forget this server") - -// onClicked: { -// UiLogic.goToPage(PageEnum.Start) -// } - } - - TextFieldWithHeaderType { - Layout.fillWidth: true - Layout.topMargin: 10 - headerText: "Server IP adress [:port]" - } - - LabelWithButtonType { - id: ip - Layout.fillWidth: true - Layout.topMargin: 10 - - text: "IP, логин и пароль от сервера" - buttonImage: "qrc:/images/controls/chevron-right.svg" - -// onClickedFunc: function() { -// UiLogic.goToPage(PageEnum.Start) -// } - } - Rectangle { - Layout.fillWidth: true - height: 1 - color: "#2C2D30" - } - LabelWithButtonType { - Layout.fillWidth: true - Layout.topMargin: 10 - - text: "QR-код, ключ или файл настроек" - buttonImage: "qrc:/images/controls/chevron-right.svg" - -// onClickedFunc: function() { -// UiLogic.goToPage(PageEnum.Start) -// } - } - Rectangle { - Layout.fillWidth: true - height: 1 - color: "#2C2D30" - } - - CardType { - Layout.fillWidth: true - Layout.topMargin: 10 - - headerText: "Высокий" - bodyText: "Многие иностранные сайты и VPN-провайдеры заблокированы" - footerText: "футер" - } - - CardType { - Layout.fillWidth: true - Layout.topMargin: 10 - - headerText: "Высокий" - bodyText: "Многие иностранные сайты и VPN-провайдеры заблокированы" - footerText: "футер" - } - - CheckBoxType { -// text: qsTr("Auto-negotiate encryption") - } - } - } -} diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 331a6aec..8d8fbb9d 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -76,75 +76,10 @@ PageBase { text: qsTr("У меня ничего нет") -// onClicked: { -// UiLogic.goToPage(PageEnum.Start) -// } - } - - DropDownType { - Layout.fillWidth: true - - text: "IP, логин и пароль от сервера" - descriptionText: "IP, логин и пароль от сервера" - - menuModel: [ - qsTr("SHA512"), - qsTr("SHA384"), - qsTr("SHA256"), - qsTr("SHA3-512"), - qsTr("SHA3-384"), - qsTr("SHA3-256"), - qsTr("whirlpool"), - qsTr("BLAKE2b512"), - qsTr("BLAKE2s256"), - qsTr("SHA1") - ] - } - CheckBoxType { -// text: qsTr("Auto-negotiate encryption") - } - CheckBoxType { - text: qsTr("Auto-negotiate encryption") - descriptionText: "dssaa" - } - - Rectangle { - implicitWidth: buttonGroup.implicitWidth - implicitHeight: buttonGroup.implicitHeight - - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - color: "#1C1D21" - radius: 16 - RowLayout { - id: buttonGroup - - spacing: 0 - - HorizontalRadioButton { - implicitWidth: (root.width - 32) / 2 - text: "ddsasdasd" - } - HorizontalRadioButton { - implicitWidth: (root.width - 32) / 2 - text: "ddsasdasd" - } + onClicked: { + UiLogic.goToPage(PageEnum.Test) } } - - VerticalRadioButton { - text: "dsasd" - descriptionText: "asd" - checked: true - - Layout.fillWidth: true - } - VerticalRadioButton { - text: "dsasd" - - Layout.fillWidth: true - } } Drawer { diff --git a/client/ui/qml/Pages2/PageTest.qml b/client/ui/qml/Pages2/PageTest.qml new file mode 100644 index 00000000..af9ef286 --- /dev/null +++ b/client/ui/qml/Pages2/PageTest.qml @@ -0,0 +1,285 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import PageEnum 1.0 + +import "./" +import "../Pages" +import "../Controls2" +import "../Config" +import "../Controls2/TextTypes" + +PageBase { + id: root + page: PageEnum.Test + logic: ViewConfigLogic + + ColumnLayout { + id: content + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + HeaderType { + id: header + + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.topMargin: 20 + Layout.bottomMargin: 32 + Layout.fillWidth: true + + buttonImage: "qrc:/images/controls/arrow-left.svg" + headerText: "Server 1" + descriptionText: "root 192.168.111.111" + } + + Item { + Layout.fillWidth: true + + TabBar { + id: tabBar + + anchors { + top: parent.top + right: parent.right + left: parent.left + } + + background: Rectangle { + color: "transparent" + } + + TabButtonType { + id: bb + isSelected: tabBar.currentIndex === 0 + text: qsTr("Протоколы") + } + TabButtonType { + isSelected: tabBar.currentIndex === 1 + text: qsTr("Сервисы") + } + TabButtonType { + isSelected: tabBar.currentIndex === 2 + text: qsTr("Данные") + } + } + + StackLayout { + id: stackLayout + currentIndex: tabBar.currentIndex + + anchors.top: tabBar.bottom + anchors.topMargin: 16 + + width: parent.width + height: root.height - header.implicitHeight - tabBar.implicitHeight - 100 + + Item { + id: protocolsTab + + FlickableType { + anchors.top: parent.top + anchors.bottom: parent.bottom + contentHeight: protocolsTabContent.height + + ColumnLayout { + id: protocolsTabContent + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + BasicButtonType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + text: qsTr("Forget this server") + } + + BasicButtonType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(255, 255, 255, 0.08) + pressedColor: Qt.rgba(255, 255, 255, 0.12) + disabledColor: "#878B91" + textColor: "#D7D8DB" + borderWidth: 1 + + text: qsTr("Forget this server") + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + headerText: "Server IP adress [:port]" + } + + LabelWithButtonType { + id: ip + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: "IP, логин и пароль от сервера" + buttonImage: "qrc:/images/controls/chevron-right.svg" + } + + Rectangle { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + height: 1 + color: "#2C2D30" + } + + LabelWithButtonType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: "QR-код, ключ или файл настроек" + buttonImage: "qrc:/images/controls/chevron-right.svg" + } + + Rectangle { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + height: 1 + color: "#2C2D30" + } + + CardType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: "Высокий" + bodyText: "Многие иностранные сайты и VPN-провайдеры заблокированы" + footerText: "футер" + } + + CardType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: "Высокий" + bodyText: "Многие иностранные сайты и VPN-провайдеры заблокированы" + footerText: "футер" + } + + DropDownType { + Layout.fillWidth: true + + text: "IP, логин и пароль от сервера" + descriptionText: "IP, логин и пароль от сервера" + + menuModel: [ + qsTr("SHA512"), + qsTr("SHA384"), + qsTr("SHA256"), + qsTr("SHA3-512"), + qsTr("SHA3-384"), + qsTr("SHA3-256"), + qsTr("whirlpool"), + qsTr("BLAKE2b512"), + qsTr("BLAKE2s256"), + qsTr("SHA1") + ] + } + } + } + } + + Item { + id: servicesTab + + FlickableType { + anchors.top: parent.top + anchors.bottom: parent.bottom + contentHeight: servicesTabContent.height + + ColumnLayout { + id: servicesTabContent + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + CheckBoxType { + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.fillWidth: true + text: qsTr("Auto-negotiate encryption") + } + CheckBoxType { + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.fillWidth: true + text: qsTr("Auto-negotiate encryption") + descriptionText: qsTr("Auto-negotiate encryption") + } + + Rectangle { + implicitWidth: buttonGroup.implicitWidth + implicitHeight: buttonGroup.implicitHeight + + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + color: "#1C1D21" + radius: 16 + + RowLayout { + id: buttonGroup + + spacing: 0 + + HorizontalRadioButton { + implicitWidth: (root.width - 32) / 2 + text: "UDP" + } + + HorizontalRadioButton { + implicitWidth: (root.width - 32) / 2 + text: "TCP" + } + } + } + + VerticalRadioButton { + text: "Раздельное туннелирование" + descriptionText: "Позволяет подключаться к одним сайтам через защищенное соединение, а к другим в обход него" + checked: true + + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + } + + VerticalRadioButton { + text: "Раздельное туннелирование" + + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + } + + SwitcherType { + text: "Auto-negotiate encryption" + + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + } + } + } + } + } + } + } +} From 68b27451f28dfc1ad634852c1dc53fc15b2b90f5 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 1 May 2023 07:29:09 +0300 Subject: [PATCH 011/278] Added scroll bar for DropDownType --- client/ui/qml/Controls2/DropDownType.qml | 143 +++++++++++++---------- 1 file changed, 79 insertions(+), 64 deletions(-) diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 8abb03d2..696aa48a 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -19,7 +19,6 @@ Item { property alias menuModel: menuContent.model - width: buttonContent.implicitWidth height: buttonContent.implicitHeight Rectangle { @@ -136,89 +135,105 @@ Item { color: Qt.rgba(14/255, 14/255, 17/255, 0.8) } - Column { + Header2TextType { + id: header + width: parent.width + + text: "Данные для подключения" + wrapMode: Text.WordWrap + anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right anchors.topMargin: 16 + anchors.leftMargin: 16 + anchors.rightMargin: 16 + } - spacing: 16 + FlickableType { + anchors.top: header.bottom + anchors.topMargin: 16 + contentHeight: col.implicitHeight - Header2TextType { - width: parent.width + Column { + id: col + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right - text: "Данные для подключения" - wrapMode: Text.WordWrap + spacing: 16 - leftPadding: 16 - rightPadding: 16 - } + ButtonGroup { + id: radioButtonGroup + } - ButtonGroup { - id: radioButtonGroup - } + ListView { + id: menuContent + width: parent.width + height: menuContent.contentItem.height - ListView { - id: menuContent - width: parent.width - height: menuContent.contentItem.height + currentIndex: -1 - currentIndex: -1 + clip: true + interactive: false - clip: true - interactive: false + delegate: Item { + implicitWidth: menuContent.width + implicitHeight: radioButton.implicitHeight - delegate: Item { - implicitWidth: menuContent.width - implicitHeight: radioButton.implicitHeight + RadioButton { + id: radioButton - RadioButton { - id: radioButton + implicitWidth: parent.width + implicitHeight: radioButtonContent.implicitHeight - implicitWidth: parent.width - implicitHeight: radioButtonContent.implicitHeight + hoverEnabled: true - hoverEnabled: true + ButtonGroup.group: radioButtonGroup - ButtonGroup.group: radioButtonGroup - - indicator: Rectangle { - anchors.fill: parent - color: radioButton.hovered ? "#2C2D30" : "#1C1D21" - } - - RowLayout { - id: radioButtonContent - anchors.fill: parent - - anchors.rightMargin: 16 - anchors.leftMargin: 16 - - z: 1 - - Text { - id: text - - text: modelData - color: "#D7D8DB" - font.pixelSize: 16 - font.weight: 400 - font.family: "PT Root UI VF" - - height: 24 - - Layout.fillWidth: true - Layout.topMargin: 20 - Layout.bottomMargin: 20 + indicator: Rectangle { + anchors.fill: parent + color: radioButton.hovered ? "#2C2D30" : "#1C1D21" } - Image { - source: "qrc:/images/controls/check.svg" - visible: radioButton.checked - width: 24 - height: 24 + RowLayout { + id: radioButtonContent + anchors.fill: parent - Layout.rightMargin: 8 + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + z: 1 + + Text { + id: text + + text: modelData + color: "#D7D8DB" + font.pixelSize: 16 + font.weight: 400 + font.family: "PT Root UI VF" + + height: 24 + + Layout.fillWidth: true + Layout.topMargin: 20 + Layout.bottomMargin: 20 + } + + Image { + source: "qrc:/images/controls/check.svg" + visible: radioButton.checked + width: 24 + height: 24 + + Layout.rightMargin: 8 + } + } + + onClicked: { + root.text = modelData + menu.visible = false } } } From 4f36349630a1ee350c63bb2717c4f75c9973ddf1 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 3 May 2023 19:06:16 +0300 Subject: [PATCH 012/278] changed the way to create qml pages, now the page is created when you go to it - added PageSetupWizardConfigSource, PageSetupWizardInstalling, PageSetupWizardProtocolSettings, PageSetupWizardTextKey --- client/amnezia_application.cpp | 6 + client/amnezia_application.h | 2 + client/images/controls/folder-open.svg | 3 + client/images/controls/qr-code.svg | 14 +++ client/images/controls/text-cursor.svg | 5 + client/resources.qrc | 7 ++ client/ui/models/containers_model.cpp | 10 ++ client/ui/models/containers_model.h | 4 + client/ui/pages.h | 4 +- .../ui/pages_logic/ServerContainersLogic.cpp | 4 +- client/ui/qml/Controls2/BasicButtonType.qml | 1 - .../ui/qml/Controls2/LabelWithButtonType.qml | 10 ++ .../qml/Controls2/TextFieldWithHeaderType.qml | 103 ++++++++++------ client/ui/qml/PageLoader.qml | 58 +-------- .../Pages2/PageSetupWizardConfigSource.qml | 114 ++++++++++++++++++ .../qml/Pages2/PageSetupWizardCredentials.qml | 6 +- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 2 +- .../qml/Pages2/PageSetupWizardInstalling.qml | 46 +++++++ .../PageSetupWizardProtocolSettings.qml | 96 +++++++++++++++ .../qml/Pages2/PageSetupWizardProtocols.qml | 12 +- .../ui/qml/Pages2/PageSetupWizardTextKey.qml | 74 ++++++++++++ client/ui/qml/Pages2/PageStart.qml | 13 +- client/ui/qml/main2.qml | 56 ++------- client/ui/uilogic.cpp | 8 +- client/ui/uilogic.h | 3 +- 25 files changed, 503 insertions(+), 158 deletions(-) create mode 100644 client/images/controls/folder-open.svg create mode 100644 client/images/controls/qr-code.svg create mode 100644 client/images/controls/text-cursor.svg create mode 100644 client/ui/qml/Pages2/PageSetupWizardConfigSource.qml create mode 100644 client/ui/qml/Pages2/PageSetupWizardInstalling.qml create mode 100644 client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml create mode 100644 client/ui/qml/Pages2/PageSetupWizardTextKey.qml diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 231b94e4..b4255a0c 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -99,6 +99,10 @@ void AmneziaApplication::init() }, Qt::QueuedConnection); m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance()); + + m_containersModel.reset(new ContainersModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get()); + m_uiLogic->registerPagesLogic(); #if defined(Q_OS_IOS) @@ -180,6 +184,8 @@ void AmneziaApplication::loadFonts() QFontDatabase::addApplicationFont(":/fonts/Lato-Regular.ttf"); QFontDatabase::addApplicationFont(":/fonts/Lato-Thin.ttf"); QFontDatabase::addApplicationFont(":/fonts/Lato-ThinItalic.ttf"); + + QFontDatabase::addApplicationFont(":/fonts/pt-root-ui_vf.ttf"); } void AmneziaApplication::loadTranslator() diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 1ac6e772..abdb1f2a 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -56,6 +56,8 @@ private: QTranslator* m_translator; QCommandLineParser m_parser; + QScopedPointer m_containersModel; + }; #endif // AMNEZIA_APPLICATION_H diff --git a/client/images/controls/folder-open.svg b/client/images/controls/folder-open.svg new file mode 100644 index 00000000..f126e5c5 --- /dev/null +++ b/client/images/controls/folder-open.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/images/controls/qr-code.svg b/client/images/controls/qr-code.svg new file mode 100644 index 00000000..a8f760fe --- /dev/null +++ b/client/images/controls/qr-code.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/client/images/controls/text-cursor.svg b/client/images/controls/text-cursor.svg new file mode 100644 index 00000000..17876d77 --- /dev/null +++ b/client/images/controls/text-cursor.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/resources.qrc b/client/resources.qrc index 1d79af1c..20eed74d 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -202,5 +202,12 @@ ui/qml/Controls2/SwitcherType.qml ui/qml/Pages2/PageTest.qml ui/qml/Controls2/TabButtonType.qml + ui/qml/Pages2/PageSetupWizardProtocolSettings.qml + ui/qml/Pages2/PageSetupWizardInstalling.qml + ui/qml/Pages2/PageSetupWizardConfigSource.qml + images/controls/folder-open.svg + images/controls/qr-code.svg + images/controls/text-cursor.svg + ui/qml/Pages2/PageSetupWizardTextKey.qml diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index 5468452e..f56613c7 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -56,4 +56,14 @@ void ContainersModel::setSelectedServerIndex(int index) endResetModel(); } +void ContainersModel::setCurrentlyInstalledContainerIndex(int index) +{ +// beginResetModel(); + m_currentlyInstalledContainerIndex = createIndex(index, 0); +// endResetModel(); +} +QString ContainersModel::getCurrentlyInstalledContainerName() +{ + return data(m_currentlyInstalledContainerIndex, NameRole).toString(); +} diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index 36206855..9c409fc0 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -27,12 +27,16 @@ public: QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; Q_INVOKABLE void setSelectedServerIndex(int index); + Q_INVOKABLE void setCurrentlyInstalledContainerIndex(int index); + + Q_INVOKABLE QString getCurrentlyInstalledContainerName(); protected: QHash roleNames() const override; private: int m_selectedServerIndex; + QModelIndex m_currentlyInstalledContainerIndex; std::shared_ptr m_settings; }; diff --git a/client/ui/pages.h b/client/ui/pages.h index 9c5db53c..32ac775d 100644 --- a/client/ui/pages.h +++ b/client/ui/pages.h @@ -28,7 +28,9 @@ enum class Page {Start = 0, NewServer, NewServerProtocols, Vpn, ProtocolSettings, ProtocolShare, QrDecoder, QrDecoderIos, About, ViewConfig, AdvancedServerSettings, ClientManagement, ClientInfo, - Test, WizardCredentials, WizardProtocols, WizardEasySetup}; + PageStart, PageTest, PageSetupWizardCredentials, PageSetupWizardProtocols, PageSetupWizardEasy, + PageSetupWizardProtocolSettings, PageSetupWizardInstalling, PageSetupWizardConfigSource, + PageSetupWizardTextKey}; Q_ENUM_NS(Page) static void declareQmlPageEnum() { diff --git a/client/ui/pages_logic/ServerContainersLogic.cpp b/client/ui/pages_logic/ServerContainersLogic.cpp index 80bf362c..2b556dcc 100644 --- a/client/ui/pages_logic/ServerContainersLogic.cpp +++ b/client/ui/pages_logic/ServerContainersLogic.cpp @@ -22,8 +22,8 @@ ServerContainersLogic::ServerContainersLogic(UiLogic *logic, QObject *parent): void ServerContainersLogic::onUpdatePage() { - ContainersModel *c_model = qobject_cast(uiLogic()->containersModel()); - c_model->setSelectedServerIndex(uiLogic()->m_selectedServerIndex); +// ContainersModel *c_model = qobject_cast(uiLogic()->containersModel()); +// c_model->setSelectedServerIndex(uiLogic()->m_selectedServerIndex); ProtocolsModel *p_model = qobject_cast(uiLogic()->protocolsModel()); p_model->setSelectedServerIndex(uiLogic()->m_selectedServerIndex); diff --git a/client/ui/qml/Controls2/BasicButtonType.qml b/client/ui/qml/Controls2/BasicButtonType.qml index cbb664e4..da5d2abf 100644 --- a/client/ui/qml/Controls2/BasicButtonType.qml +++ b/client/ui/qml/Controls2/BasicButtonType.qml @@ -14,7 +14,6 @@ Button { property string borderColor: "#D7D8DB" property int borderWidth: 0 - implicitWidth: 328 implicitHeight: 56 hoverEnabled: true diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml index 92606940..b6d113bb 100644 --- a/client/ui/qml/Controls2/LabelWithButtonType.qml +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -9,7 +9,9 @@ Item { property string descriptionText property var onClickedFunc + property alias buttonImage: button.image + property string iconImage implicitWidth: content.implicitWidth implicitHeight: content.implicitHeight @@ -18,6 +20,13 @@ Item { id: content anchors.fill: parent + Image { + id: icon + source: iconImage + visible: iconImage ? true : false + Layout.rightMargin: visible ? 16 : 0 + } + ColumnLayout { Text { font.family: "PT Root UI" @@ -25,6 +34,7 @@ Item { font.pixelSize: 18 color: "#d7d8db" text: root.text + wrapMode: Text.WordWrap Layout.fillWidth: true height: 22 diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml index 350306bb..3458f741 100644 --- a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -10,7 +10,11 @@ Item { property string textFieldPlaceholderText property bool textFieldEditable: true - implicitWidth: 328 + property string buttonText + property var clickedFunc + + property alias textField: textField + implicitHeight: 74 Rectangle { @@ -26,50 +30,77 @@ Item { } } - ColumnLayout { + RowLayout { anchors.fill: backgroud + ColumnLayout { - Text { - text: root.headerText - color: "#878b91" - font.pixelSize: 13 - font.weight: 400 - font.family: "PT Root UI VF" - font.letterSpacing: 0.02 + Text { + text: root.headerText + color: "#878b91" + font.pixelSize: 13 + font.weight: 400 + font.family: "PT Root UI VF" + font.letterSpacing: 0.02 - height: 16 - Layout.fillWidth: true - Layout.rightMargin: 16 - Layout.leftMargin: 16 - Layout.topMargin: 16 + height: 16 + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.topMargin: 16 + } + + TextField { + id: textField + + enabled: root.textFieldEditable + text: root.textFieldText + color: "#d7d8db" + + placeholderText: textFieldPlaceholderText + placeholderTextColor: "#494B50" + + selectionColor: "#412102" + selectedTextColor: "#D7D8DB" + + font.pixelSize: 16 + font.weight: 400 + font.family: "PT Root UI VF" + + height: 24 + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.bottomMargin: 16 + topPadding: 0 + rightPadding: 0 + leftPadding: 0 + bottomPadding: 0 + + background: Rectangle { + anchors.fill: parent + color: "#1c1d21" + } + } } - TextField { - id: textField + BasicButtonType { + visible: root.buttonText !== "" - enabled: root.textFieldEditable - text: root.textFieldText - color: "#d7d8db" + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#D7D8DB" + borderWidth: 0 - placeholderText: textFieldPlaceholderText + text: buttonText - font.pixelSize: 16 - font.weight: 400 - font.family: "PT Root UI VF" + Layout.rightMargin: 24 - height: 24 - Layout.fillWidth: true - Layout.rightMargin: 16 - Layout.leftMargin: 16 - Layout.bottomMargin: 16 - topPadding: 0 - rightPadding: 0 - leftPadding: 0 - bottomPadding: 0 - - background: Rectangle { - anchors.fill: parent - color: "#1c1d21" + onClicked: { + if (clickedFunc && typeof clickedFunc === "function") { + clickedFunc() + } } } } diff --git a/client/ui/qml/PageLoader.qml b/client/ui/qml/PageLoader.qml index 5f2fc3c2..a44fa4b5 100644 --- a/client/ui/qml/PageLoader.qml +++ b/client/ui/qml/PageLoader.qml @@ -1,57 +1,7 @@ import QtQuick +import QtQuick.Controls -import Qt.labs.folderlistmodel - -import PageType 1.0 - -Item { - property var pages: ({}) - - signal finished() - - FolderListModel { - id: folderModelPages - folder: "qrc:/ui/qml/Pages2/" - nameFilters: ["*.qml"] - showDirs: false - - onStatusChanged: { - if (status == FolderListModel.Ready) { - for (var i = 0; i < folderModelPages.count; i++) { - createPagesObjects(folderModelPages.get(i, "filePath"), PageType.Basic); - } - finished() - } - } - - function createPagesObjects(file, type) { - if (file.indexOf("Base") !== -1) { - return; // skip Base Pages - } - - var c = Qt.createComponent("qrc" + file); - - var finishCreation = function(component) { - if (component.status === Component.Ready) { - var obj = component.createObject(root); - if (obj === null) { - console.debug("Error creating object " + component.url); - } else { - obj.visible = false - if (type === PageType.Basic) { - pages[obj.page] = obj - } - } - } else if (component.status === Component.Error) { - console.debug("Error loading component:", component.errorString()); - } - } - - if (c.status === Component.Ready) { - finishCreation(c); - } else { - console.debug("Warning: " + file + " page components are not ready " + c.errorString()); - } - } - } +StackView { + id: stackView + initialItem: "PageStart" } diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml new file mode 100644 index 00000000..26f25a15 --- /dev/null +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -0,0 +1,114 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs + +import PageEnum 1.0 + +import "./" +import "../Pages" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +PageBase { + id: root + page: PageEnum.PageSetupWizardInstalling + + FlickableType { + id: fl + 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 + + spacing: 16 + + HeaderType { + Layout.fillWidth: true + Layout.topMargin: 20 + + buttonImage: "qrc:/images/controls/arrow-left.svg" + + headerText: "Подключение к серверу" + descriptionText: "Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватывать ваши данные.\n +Всё в порядке, если код передал друг." + } + + Header2TextType { + Layout.fillWidth: true + Layout.topMargin: 32 + + text: "Что у вас есть?" + } + + LabelWithButtonType { + Layout.fillWidth: true + Layout.topMargin: 16 + + text: "Файл с настройками подключения" + buttonImage: "qrc:/images/controls/chevron-right.svg" + iconImage: "qrc:/images/controls/folder-open.svg" + + onClickedFunc: function() { + onClicked: fileDialog.open() + } + + FileDialog { + id: fileDialog +// currentFolder: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0] + onAccepted: { + + } + } + } + Rectangle { + Layout.fillWidth: true + height: 1 + color: "#2C2D30" + } + + //todo ifdef mobile platforms> + LabelWithButtonType { + Layout.fillWidth: true + + text: "QR-код" + buttonImage: "qrc:/images/controls/chevron-right.svg" + iconImage: "qrc:/images/controls/qr-code.svg" + + onClickedFunc: function() { + } + } + Rectangle { + Layout.fillWidth: true + height: 1 + color: "#2C2D30" + } + + LabelWithButtonType { + Layout.fillWidth: true + + text: "Ключ в виде текста" + buttonImage: "qrc:/images/controls/chevron-right.svg" + iconImage: "qrc:/images/controls/text-cursor.svg" + + onClickedFunc: function() { + UiLogic.goToPage(PageEnum.PageSetupWizardTextKey) + } + } + Rectangle { + Layout.fillWidth: true + height: 1 + color: "#2C2D30" + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index 20472de2..a6e98fee 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -11,7 +11,7 @@ import "../Config" PageBase { id: root - page: PageEnum.WizardCredentials + page: PageEnum.PageSetupWizardCredentials FlickableType { id: fl @@ -61,7 +61,7 @@ PageBase { text: qsTr("Настроить сервер простым образом") onClicked: function() { - UiLogic.goToPage(PageEnum.WizardEasySetup) + UiLogic.goToPage(PageEnum.PageSetupWizardEasy) } } @@ -79,7 +79,7 @@ PageBase { text: qsTr("Выбрать протокол для установки") onClicked: function() { - UiLogic.goToPage(PageEnum.WizardProtocols) + UiLogic.goToPage(PageEnum.PageSetupWizardProtocols) } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index b4121592..8c75a1df 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -11,7 +11,7 @@ import "../Config" PageBase { id: root - page: PageEnum.WizardEasySetup + page: PageEnum.PageSetupWizardEasy FlickableType { id: fl diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml new file mode 100644 index 00000000..3445b533 --- /dev/null +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -0,0 +1,46 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "./" +import "../Pages" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +PageBase { + id: root + page: PageEnum.PageSetupWizardInstalling + + FlickableType { + id: fl + 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 + + spacing: 16 + + HeaderType { + Layout.fillWidth: true + Layout.topMargin: 20 + + //TODO remove later + buttonImage: "qrc:/images/controls/arrow-left.svg" + + headerText: "Установка" + descriptionText: ContainersModel.getCurrentlyInstalledContainerName() + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml new file mode 100644 index 00000000..9b6e788f --- /dev/null +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -0,0 +1,96 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "./" +import "../Pages" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +PageBase { + id: root + page: PageEnum.PageSetupWizardProtocolSettings + + FlickableType { + id: fl + 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 + + spacing: 16 + + HeaderType { + Layout.fillWidth: true + Layout.topMargin: 20 + + buttonImage: "qrc:/images/controls/arrow-left.svg" + + headerText: "Установка " + ContainersModel.getCurrentlyInstalledContainerName() + descriptionText: "Эти настройки можно будет изменить позже" + } + + BodyTextType { + Layout.topMargin: 16 + + text: "Network protocol" + } + + //TODO move to separete control + Rectangle { + implicitWidth: buttonGroup.implicitWidth + implicitHeight: buttonGroup.implicitHeight + + color: "#1C1D21" + radius: 16 + + RowLayout { + id: buttonGroup + + spacing: 0 + + HorizontalRadioButton { + implicitWidth: (root.width - 32) / 2 + text: "UDP" + } + + HorizontalRadioButton { + implicitWidth: (root.width - 32) / 2 + text: "TCP" + } + } + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + headerText: "Port" + } + } + } + + BasicButtonType { + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + anchors.bottomMargin: 32 + + text: qsTr("Установить") + + onClicked: function() { + UiLogic.goToPage(PageEnum.PageSetupWizardInstalling) + } + } +} diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index a50da64b..ad4efbc2 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -14,11 +14,11 @@ import "../Config" PageBase { id: root - page: PageEnum.WizardProtocols + page: PageEnum.PageSetupWizardProtocols SortFilterProxyModel { - id: containersModel - sourceModel: UiLogic.containersModel + id: proxyContainersModel + sourceModel: ContainersModel filters: [ ValueFilter { roleName: "is_installed_role" @@ -64,7 +64,7 @@ PageBase { currentIndex: -1 clip: true interactive: false - model: containersModel + model: proxyContainersModel delegate: Item { implicitWidth: containers.width @@ -87,6 +87,10 @@ PageBase { descriptionText: desc_role buttonImage: "qrc:/images/controls/chevron-right.svg" + onClickedFunc: function() { + ContainersModel.setCurrentlyInstalledContainerIndex(proxyContainersModel.mapToSource(index)) + UiLogic.goToPage(PageEnum.PageSetupWizardProtocolSettings) + } } Rectangle { diff --git a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml new file mode 100644 index 00000000..9fddc573 --- /dev/null +++ b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml @@ -0,0 +1,74 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "./" +import "../Pages" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +PageBase { + id: root + page: PageEnum.PageSetupWizardInstalling + + FlickableType { + id: fl + 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 + + spacing: 16 + + HeaderType { + Layout.fillWidth: true + Layout.topMargin: 20 + + buttonImage: "qrc:/images/controls/arrow-left.svg" + + headerText: "Ключ для подключения" + descriptionText: "Строка, которая начинается с vpn://..." + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + Layout.topMargin: 32 + + headerText: "Ключ" + textFieldPlaceholderText: "vpn://" + buttonText: "Вставить" + + clickedFunc: function() { + textField.text = "" + textField.paste() + } + } + } + } + + BasicButtonType { + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + anchors.bottomMargin: 32 + + text: qsTr("Подключиться") + + onClicked: function() { +// UiLogic.goToPage(PageEnum.PageSetupWizardInstalling) + } + } +} diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 8d8fbb9d..e04ebbb6 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -12,7 +12,7 @@ import "../Controls2/TextTypes" PageBase { id: root - page: PageEnum.Start + page: PageEnum.PageStart FlickableType { id: fl @@ -77,7 +77,7 @@ PageBase { text: qsTr("У меня ничего нет") onClicked: { - UiLogic.goToPage(PageEnum.Test) + UiLogic.goToPage(PageEnum.PageTest) } } } @@ -129,7 +129,7 @@ PageBase { buttonImage: "qrc:/images/controls/chevron-right.svg" onClickedFunc: function() { - UiLogic.goToPage(PageEnum.WizardCredentials) + UiLogic.goToPage(PageEnum.PageSetupWizardCredentials) drawer.visible = false } } @@ -144,9 +144,10 @@ PageBase { text: "QR-код, ключ или файл настроек" buttonImage: "qrc:/images/controls/chevron-right.svg" - // onClickedFunc: function() { - // UiLogic.goToPage(PageEnum.Start) - // } + onClickedFunc: function() { + UiLogic.goToPage(PageEnum.PageSetupWizardConfigSource) + drawer.visible = false + } } Rectangle { Layout.fillWidth: true diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index c7e06eaa..6261e9a7 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -14,6 +14,7 @@ Window { height: GC.screenHeight minimumWidth: GC.isDesktop() ? 360 : 0 minimumHeight: GC.isDesktop() ? 640 : 0 + onClosing: function() { console.debug("QML onClosing signal") UiLogic.onCloseWindow() @@ -21,28 +22,18 @@ Window { title: "AmneziaVPN" - function gotoPage(type, page, reset, slide) { - let p_obj; - if (type === PageType.Basic) p_obj = pageLoader.pages[page] - else if (type === PageType.Proto) p_obj = protocolPages[page] - else if (type === PageType.ShareProto) p_obj = sharePages[page] - else return - + function gotoPage(page, reset, slide) { if (pageStackView.depth > 0) { pageStackView.currentItem.deactivated() } if (slide) { - pageStackView.push(p_obj, {}, StackView.PushTransition) + pageStackView.push(UiLogic.pageEnumToString(page), {}, StackView.PushTransition) } else { - pageStackView.push(p_obj, {}, StackView.Immediate) + pageStackView.push(UiLogic.pageEnumToString(page), {}, StackView.Immediate) } -// if (reset) { -// p_obj.logic.onUpdatePage(); -// } - - p_obj.activated(reset) + pageStackView.currentItem.activated(reset) } function closePage() { @@ -60,9 +51,9 @@ Window { pageStackView.clear() if (slide) { - pageStackView.push(pages[page], {}, StackView.PushTransition) + pageStackView.push(UiLogic.pageEnumToString(page), {}, StackView.PushTransition) } else { - pageStackView.push(pages[page], {}, StackView.Immediate) + pageStackView.push(UiLogic.pageEnumToString(page), {}, StackView.Immediate) } if (page === PageEnum.Start) { UiLogic.pushButtonBackFromStartVisible = !pageStackView.empty @@ -79,33 +70,12 @@ Window { id: pageStackView anchors.fill: parent focus: true - - onCurrentItemChanged: function() { - UiLogic.currentPageValue = currentItem.page - } - - onDepthChanged: function() { - UiLogic.pagesStackDepth = depth - } - - Keys.onPressed: function(event) { - UiLogic.keyPressEvent(event.key) - event.accepted = true - } } Connections { target: UiLogic function onGoToPage(page, reset, slide) { - root.gotoPage(PageType.Basic, page, reset, slide) - } - - function onGoToProtocolPage(protocol, reset, slide) { - root.gotoPage(PageType.Proto, protocol, reset, slide) - } - - function onGoToShareProtocolPage(protocol, reset, slide) { - root.gotoPage(PageType.ShareProto, protocol, reset, slide) + root.gotoPage(page, reset, slide) } function onClosePage() { @@ -118,6 +88,7 @@ Window { function onShow() { root.show() + UiLogic.initializeUiLogic() } function onHide() { @@ -130,13 +101,4 @@ Window { root.requestActivate() } } - - PageLoader { - id: pageLoader - - onFinished: { - UiLogic.initializeUiLogic() - } - } - } diff --git a/client/ui/uilogic.cpp b/client/ui/uilogic.cpp index d47c1186..63371420 100644 --- a/client/ui/uilogic.cpp +++ b/client/ui/uilogic.cpp @@ -84,7 +84,6 @@ UiLogic::UiLogic(std::shared_ptr settings, std::shared_ptrdefaultServerIndex(); @@ -619,3 +618,8 @@ bool UiLogic::isContainerAlreadyAddedToGui(DockerContainer container) return false; } +QString UiLogic::pageEnumToString(Page page) { + QMetaEnum metaEnum = QMetaEnum::fromType(); + QString pageName = metaEnum.valueToKey(static_cast(page)); + return "Pages2/" + pageName + ".qml"; +} diff --git a/client/ui/uilogic.h b/client/ui/uilogic.h index 3b7797a8..4b392066 100644 --- a/client/ui/uilogic.h +++ b/client/ui/uilogic.h @@ -67,7 +67,6 @@ class UiLogic : public QObject AUTO_PROPERTY(int, pagesStackDepth) AUTO_PROPERTY(int, currentPageValue) - READONLY_PROPERTY(QObject *, containersModel) READONLY_PROPERTY(QObject *, protocolsModel) READONLY_PROPERTY(QObject *, clientManagementModel) @@ -124,6 +123,8 @@ public: Q_INVOKABLE amnezia::ErrorCode addAlreadyInstalledContainersGui(bool &isServerCreated); + Q_INVOKABLE QString pageEnumToString(PageEnumNS::Page page); + void shareTempFile(const QString &suggestedName, QString ext, const QString& data); static QString getOpenFileName(QWidget *parent = nullptr, const QString &caption = QString(), From 1c8dbae35913d6eb4b0f55203665f8db63246027 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sat, 6 May 2023 06:52:23 +0300 Subject: [PATCH 013/278] added PageHome, PageSettings, PageShare, PageStart - renamed old PageStart to PageSetupWizardStart - added various text types - moved servers model to "global" scope --- client/amnezia_application.cpp | 3 + client/amnezia_application.h | 3 + client/images/controls/home.svg | 4 + client/images/controls/settings-2.svg | 6 + client/images/controls/share-2.svg | 7 + client/resources.qrc | 16 +- client/ui/models/servers_model.cpp | 35 ++- client/ui/models/servers_model.h | 11 +- client/ui/pages.h | 6 +- client/ui/pages_logic/ServerListLogic.cpp | 4 +- client/ui/qml/Controls2/DropDownType.qml | 61 ++-- client/ui/qml/Controls2/Header2Type.qml | 56 ++++ client/ui/qml/Controls2/HeaderType.qml | 32 +-- .../ui/qml/Controls2/TabImageButtonType.qml | 24 ++ .../Controls2/TextTypes/ButtonTextType.qml | 12 + .../Controls2/TextTypes/Header1TextType.qml | 14 + .../Controls2/TextTypes/Header2TextType.qml | 4 +- .../qml/Controls2/TextTypes/LabelTextType.qml | 13 + ...BodyTextType.qml => ParagraphTextType.qml} | 6 +- client/ui/qml/PageLoader.qml | 2 +- client/ui/qml/Pages2/PageHome.qml | 266 ++++++++++++++++++ client/ui/qml/Pages2/PageSettings.qml | 5 + .../Pages2/PageSetupWizardConfigSource.qml | 3 +- .../PageSetupWizardProtocolSettings.qml | 2 +- client/ui/qml/Pages2/PageSetupWizardStart.qml | 163 +++++++++++ client/ui/qml/Pages2/PageShare.qml | 5 + client/ui/qml/Pages2/PageStart.qml | 194 ++++--------- client/ui/uilogic.cpp | 4 +- 28 files changed, 735 insertions(+), 226 deletions(-) create mode 100644 client/images/controls/home.svg create mode 100644 client/images/controls/settings-2.svg create mode 100644 client/images/controls/share-2.svg create mode 100644 client/ui/qml/Controls2/Header2Type.qml create mode 100644 client/ui/qml/Controls2/TabImageButtonType.qml create mode 100644 client/ui/qml/Controls2/TextTypes/ButtonTextType.qml create mode 100644 client/ui/qml/Controls2/TextTypes/Header1TextType.qml create mode 100644 client/ui/qml/Controls2/TextTypes/LabelTextType.qml rename client/ui/qml/Controls2/TextTypes/{BodyTextType.qml => ParagraphTextType.qml} (87%) create mode 100644 client/ui/qml/Pages2/PageHome.qml create mode 100644 client/ui/qml/Pages2/PageSettings.qml create mode 100644 client/ui/qml/Pages2/PageSetupWizardStart.qml create mode 100644 client/ui/qml/Pages2/PageShare.qml diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index b4255a0c..2cd61679 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -103,6 +103,9 @@ void AmneziaApplication::init() m_containersModel.reset(new ContainersModel(m_settings, this)); m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get()); + m_serversModel.reset(new ServersModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get()); + m_uiLogic->registerPagesLogic(); #if defined(Q_OS_IOS) diff --git a/client/amnezia_application.h b/client/amnezia_application.h index abdb1f2a..d15864e1 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -12,6 +12,8 @@ #include "ui/uilogic.h" #include "configurators/vpn_configurator.h" +#include "ui/models/servers_model.h" +#include "ui/models/containers_model.h" #define amnApp (static_cast(QCoreApplication::instance())) @@ -57,6 +59,7 @@ private: QCommandLineParser m_parser; QScopedPointer m_containersModel; + QScopedPointer m_serversModel; }; diff --git a/client/images/controls/home.svg b/client/images/controls/home.svg new file mode 100644 index 00000000..5ade1d33 --- /dev/null +++ b/client/images/controls/home.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/images/controls/settings-2.svg b/client/images/controls/settings-2.svg new file mode 100644 index 00000000..9f1cf974 --- /dev/null +++ b/client/images/controls/settings-2.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/client/images/controls/share-2.svg b/client/images/controls/share-2.svg new file mode 100644 index 00000000..b0aa6331 --- /dev/null +++ b/client/images/controls/share-2.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/client/resources.qrc b/client/resources.qrc index 20eed74d..f5817eb0 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -182,7 +182,7 @@ ui/qml/Controls2/CheckBoxType.qml images/controls/check.svg ui/qml/Controls2/DropDownType.qml - ui/qml/Pages2/PageStart.qml + ui/qml/Pages2/PageSetupWizardStart.qml ui/qml/main2.qml ui/qml/PageLoader.qml images/amneziaBigLogo.png @@ -195,7 +195,7 @@ ui/qml/Pages2/PageSetupWizardEasy.qml images/controls/chevron-down.svg images/controls/chevron-up.svg - ui/qml/Controls2/TextTypes/BodyTextType.qml + ui/qml/Controls2/TextTypes/ParagraphTextType.qml ui/qml/Controls2/TextTypes/Header2TextType.qml ui/qml/Controls2/HorizontalRadioButton.qml ui/qml/Controls2/VerticalRadioButton.qml @@ -209,5 +209,17 @@ images/controls/qr-code.svg images/controls/text-cursor.svg ui/qml/Pages2/PageSetupWizardTextKey.qml + ui/qml/Pages2/PageStart.qml + ui/qml/Controls2/TabImageButtonType.qml + images/controls/home.svg + images/controls/settings-2.svg + images/controls/share-2.svg + ui/qml/Pages2/PageHome.qml + ui/qml/Pages2/PageSettings.qml + ui/qml/Pages2/PageShare.qml + ui/qml/Controls2/TextTypes/Header1TextType.qml + ui/qml/Controls2/TextTypes/LabelTextType.qml + ui/qml/Controls2/TextTypes/ButtonTextType.qml + ui/qml/Controls2/Header2Type.qml diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index c4b0efb3..94d3ced4 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -1,29 +1,42 @@ #include "servers_model.h" -ServersModel::ServersModel(QObject *parent) : - QAbstractListModel(parent) +ServersModel::ServersModel(std::shared_ptr settings, QObject *parent) : m_settings(settings), QAbstractListModel(parent) { - + const QJsonArray &servers = m_settings->serversArray(); + int defaultServer = m_settings->defaultServerIndex(); + QVector serverListContent; + for(int i = 0; i < servers.size(); i++) { + ServerModelContent c; + auto server = servers.at(i).toObject(); + c.desc = server.value(config_key::description).toString(); + c.address = server.value(config_key::hostName).toString(); + if (c.desc.isEmpty()) { + c.desc = c.address; + } + c.isDefault = (i == defaultServer); + serverListContent.push_back(c); + } + setContent(serverListContent); } void ServersModel::clearData() { beginResetModel(); - content.clear(); + m_content.clear(); endResetModel(); } -void ServersModel::setContent(const std::vector &data) +void ServersModel::setContent(const QVector &data) { beginResetModel(); - content = data; + m_content = data; endResetModel(); } int ServersModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); - return static_cast(content.size()); + return static_cast(m_content.size()); } QHash ServersModel::roleNames() const { @@ -37,17 +50,17 @@ QHash ServersModel::roleNames() const { QVariant ServersModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() < 0 - || index.row() >= static_cast(content.size())) { + || index.row() >= static_cast(m_content.size())) { return QVariant(); } if (role == DescRole) { - return content[index.row()].desc; + return m_content[index.row()].desc; } if (role == AddressRole) { - return content[index.row()].address; + return m_content[index.row()].address; } if (role == IsDefaultRole) { - return content[index.row()].isDefault; + return m_content[index.row()].isDefault; } return QVariant(); } diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index 7f2e3ad1..e60aea6b 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -2,8 +2,8 @@ #define SERVERSMODEL_H #include -#include -#include + +#include "settings.h" struct ServerModelContent { QString desc; @@ -15,7 +15,7 @@ class ServersModel : public QAbstractListModel { Q_OBJECT public: - ServersModel(QObject *parent = nullptr); + ServersModel(std::shared_ptr settings, QObject *parent = nullptr); public: enum SiteRoles { DescRole = Qt::UserRole + 1, @@ -24,7 +24,7 @@ public: }; void clearData(); - void setContent(const std::vector& data); + void setContent(const QVector& data); int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; @@ -33,7 +33,8 @@ protected: QHash roleNames() const override; private: - std::vector content; + QVector m_content; + std::shared_ptr m_settings; }; #endif // SERVERSMODEL_H diff --git a/client/ui/pages.h b/client/ui/pages.h index 32ac775d..b2e191c7 100644 --- a/client/ui/pages.h +++ b/client/ui/pages.h @@ -28,9 +28,11 @@ enum class Page {Start = 0, NewServer, NewServerProtocols, Vpn, ProtocolSettings, ProtocolShare, QrDecoder, QrDecoderIos, About, ViewConfig, AdvancedServerSettings, ClientManagement, ClientInfo, - PageStart, PageTest, PageSetupWizardCredentials, PageSetupWizardProtocols, PageSetupWizardEasy, + PageSetupWizardStart, PageTest, PageSetupWizardCredentials, PageSetupWizardProtocols, PageSetupWizardEasy, PageSetupWizardProtocolSettings, PageSetupWizardInstalling, PageSetupWizardConfigSource, - PageSetupWizardTextKey}; + PageSetupWizardTextKey, + + PageStart, PageHome, PageSettings, PageShare}; Q_ENUM_NS(Page) static void declareQmlPageEnum() { diff --git a/client/ui/pages_logic/ServerListLogic.cpp b/client/ui/pages_logic/ServerListLogic.cpp index 79d13c8b..4a17e202 100644 --- a/client/ui/pages_logic/ServerListLogic.cpp +++ b/client/ui/pages_logic/ServerListLogic.cpp @@ -6,7 +6,7 @@ ServerListLogic::ServerListLogic(UiLogic *logic, QObject *parent): PageLogicBase(logic, parent), - m_serverListModel{new ServersModel(this)} + m_serverListModel{new ServersModel(m_settings, this)} { } @@ -33,7 +33,7 @@ void ServerListLogic::onUpdatePage() { const QJsonArray &servers = m_settings->serversArray(); int defaultServer = m_settings->defaultServerIndex(); - std::vector serverListContent; + QVector serverListContent; for(int i = 0; i < servers.size(); i++) { ServerModelContent c; auto server = servers.at(i).toObject(); diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 696aa48a..6f511194 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -12,14 +12,20 @@ Item { property var onClickedFunc property string buttonImage: "qrc:/images/controls/chevron-down.svg" + property string buttonImageColor: "#494B50" + property string defaultColor: "#1C1D21" + property string textColor: "#d7d8db" + property string borderColor: "#494B50" + property int borderWidth: 1 property alias menuModel: menuContent.model - height: buttonContent.implicitHeight + implicitWidth: buttonContent.implicitWidth + implicitHeight: buttonContent.implicitHeight Rectangle { id: buttonBackground @@ -28,6 +34,7 @@ Item { radius: 16 color: defaultColor border.color: borderColor + border.width: borderWidth Behavior on border.width { PropertyAnimation { duration: 200 } @@ -37,72 +44,55 @@ Item { RowLayout { id: buttonContent anchors.fill: parent - anchors.rightMargin: 16 - anchors.leftMargin: 16 + + spacing: 0 ColumnLayout { Layout.leftMargin: 16 - Layout.rightMargin: 16 - Layout.topMargin: 16 - Layout.bottomMargin: 16 - Text { + LabelTextType { + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + visible: root.descriptionText !== "" - font.family: "PT Root UI" - font.styleName: "normal" - font.pixelSize: 13 - font.letterSpacing: 0.02 color: "#878B91" text: root.descriptionText - wrapMode: Text.WordWrap - - Layout.fillWidth: true - height: 16 - - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter } - Text { - font.family: "PT Root UI" - font.styleName: "normal" - font.pixelSize: 16 - color: "#d7d8db" - text: root.text - - Layout.fillWidth: true - height: 24 - - horizontalAlignment: Text.AlignLeft + ButtonTextType { + horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter + + color: root.textColor + text: root.text } } ImageButtonType { id: button + Layout.leftMargin: 4 Layout.rightMargin: 16 hoverEnabled: false image: buttonImage + imageColor: buttonImageColor onClicked: { if (onClickedFunc && typeof onClickedFunc === "function") { onClickedFunc() } } - - Layout.alignment: Qt.AlignRight } } MouseArea { - anchors.fill: parent + anchors.fill: buttonContent cursorShape: Qt.PointingHandCursor hoverEnabled: true onEntered: { - buttonBackground.border.width = 1 + buttonBackground.border.width = borderWidth } onExited: { @@ -119,7 +109,7 @@ Item { edge: Qt.BottomEdge width: parent.width - height: parent.height * 0.8 + height: parent.height * 0.9 clip: true modal: true @@ -129,6 +119,9 @@ Item { anchors.bottomMargin: -radius radius: 16 color: "#1C1D21" + + border.color: borderColor + border.width: 1 } Overlay.modal: Rectangle { diff --git a/client/ui/qml/Controls2/Header2Type.qml b/client/ui/qml/Controls2/Header2Type.qml new file mode 100644 index 00000000..e0173a73 --- /dev/null +++ b/client/ui/qml/Controls2/Header2Type.qml @@ -0,0 +1,56 @@ +import QtQuick +import QtQuick.Layouts + +import "TextTypes" + +Item { + id: root + + property string buttonImage + property string headerText + property string descriptionText + + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight + + ColumnLayout { + id: content + anchors.fill: parent + + ImageButtonType { + id: backButton + + Layout.leftMargin: -6 + + image: root.buttonImage + imageColor: "#D7D8DB" + + visible: image ? true : false + + onClicked: { + UiLogic.closePage() + } + } + + Header2TextType { + id: header + + Layout.fillWidth: true + + text: root.headerText + } + + ParagraphTextType { + id: description + + Layout.topMargin: 16 + Layout.fillWidth: true + + text: root.descriptionText + + color: "#878B91" + + visible: root.descriptionText !== "" + } + } +} diff --git a/client/ui/qml/Controls2/HeaderType.qml b/client/ui/qml/Controls2/HeaderType.qml index 6e10e75c..407f67f0 100644 --- a/client/ui/qml/Controls2/HeaderType.qml +++ b/client/ui/qml/Controls2/HeaderType.qml @@ -1,6 +1,8 @@ import QtQuick import QtQuick.Layouts +import "TextTypes" + Item { id: root @@ -30,39 +32,23 @@ Item { } } - Text { + Header1TextType { id: header - text: root.headerText - - color: "#D7D8DB" - font.pixelSize: 36 - font.weight: 700 - font.family: "PT Root UI VF" - font.letterSpacing: -0.03 - - wrapMode: Text.WordWrap - - height: 38 Layout.fillWidth: true + + text: root.headerText } - Text { + ParagraphTextType { id: description + Layout.topMargin: 16 + Layout.fillWidth: true + text: root.descriptionText color: "#878B91" - font.pixelSize: 16 - font.weight: 400 - font.family: "PT Root UI VF" - font.letterSpacing: -0.03 - - wrapMode: Text.WordWrap - - height: 24 - Layout.topMargin: 16 - Layout.fillWidth: true } } } diff --git a/client/ui/qml/Controls2/TabImageButtonType.qml b/client/ui/qml/Controls2/TabImageButtonType.qml new file mode 100644 index 00000000..06b77e6a --- /dev/null +++ b/client/ui/qml/Controls2/TabImageButtonType.qml @@ -0,0 +1,24 @@ +import QtQuick +import QtQuick.Controls + +TabButton { + id: root + + property string hoveredColor: "#412102" + property string defaultColor: "#D7D8DB" + property string selectedColor: "#FBB26A" + + property string image + + property bool isSelected: false + + hoverEnabled: true + + icon.source: image + icon.color: isSelected ? selectedColor : defaultColor + + background: Rectangle { + anchors.fill: parent + color: "transparent" + } +} diff --git a/client/ui/qml/Controls2/TextTypes/ButtonTextType.qml b/client/ui/qml/Controls2/TextTypes/ButtonTextType.qml new file mode 100644 index 00000000..93a36578 --- /dev/null +++ b/client/ui/qml/Controls2/TextTypes/ButtonTextType.qml @@ -0,0 +1,12 @@ +import QtQuick + +Text { + height: 24 + + color: "#D7D8DB" + font.pixelSize: 16 + font.weight: 500 + font.family: "PT Root UI VF" + + wrapMode: Text.WordWrap +} diff --git a/client/ui/qml/Controls2/TextTypes/Header1TextType.qml b/client/ui/qml/Controls2/TextTypes/Header1TextType.qml new file mode 100644 index 00000000..dbc04b6a --- /dev/null +++ b/client/ui/qml/Controls2/TextTypes/Header1TextType.qml @@ -0,0 +1,14 @@ +import QtQuick + +Text { + height: 38 + + color: "#D7D8DB" + font.pixelSize: 36 + font.weight: 700 + font.family: "PT Root UI VF" + font.letterSpacing: -0.03 + + wrapMode: Text.WordWrap +} + diff --git a/client/ui/qml/Controls2/TextTypes/Header2TextType.qml b/client/ui/qml/Controls2/TextTypes/Header2TextType.qml index 4bbbc0d6..1400ceb2 100644 --- a/client/ui/qml/Controls2/TextTypes/Header2TextType.qml +++ b/client/ui/qml/Controls2/TextTypes/Header2TextType.qml @@ -1,10 +1,12 @@ import QtQuick Text { + height: 30 + color: "#D7D8DB" font.pixelSize: 25 font.weight: 700 font.family: "PT Root UI VF" - height: 30 + wrapMode: Text.WordWrap } diff --git a/client/ui/qml/Controls2/TextTypes/LabelTextType.qml b/client/ui/qml/Controls2/TextTypes/LabelTextType.qml new file mode 100644 index 00000000..38649022 --- /dev/null +++ b/client/ui/qml/Controls2/TextTypes/LabelTextType.qml @@ -0,0 +1,13 @@ +import QtQuick + +Text { + height: 16 + + color: "#878B91" + font.pixelSize: 13 + font.weight: 400 + font.family: "PT Root UI VF" + font.letterSpacing: 0.02 + + wrapMode: Text.WordWrap +} diff --git a/client/ui/qml/Controls2/TextTypes/BodyTextType.qml b/client/ui/qml/Controls2/TextTypes/ParagraphTextType.qml similarity index 87% rename from client/ui/qml/Controls2/TextTypes/BodyTextType.qml rename to client/ui/qml/Controls2/TextTypes/ParagraphTextType.qml index 9d789385..269830bc 100644 --- a/client/ui/qml/Controls2/TextTypes/BodyTextType.qml +++ b/client/ui/qml/Controls2/TextTypes/ParagraphTextType.qml @@ -1,12 +1,12 @@ import QtQuick Text { - text: root.bodyText - wrapMode: Text.WordWrap + height: 24 + color: "#D7D8DB" font.pixelSize: 16 font.weight: 400 font.family: "PT Root UI VF" - height: 24 + wrapMode: Text.WordWrap } diff --git a/client/ui/qml/PageLoader.qml b/client/ui/qml/PageLoader.qml index a44fa4b5..86f9e5a1 100644 --- a/client/ui/qml/PageLoader.qml +++ b/client/ui/qml/PageLoader.qml @@ -3,5 +3,5 @@ import QtQuick.Controls StackView { id: stackView - initialItem: "PageStart" + initialItem: "PageSetupWizardStart" } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml new file mode 100644 index 00000000..ad1b7d38 --- /dev/null +++ b/client/ui/qml/Pages2/PageHome.qml @@ -0,0 +1,266 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "./" +import "../Pages" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +PageBase { + id: root + page: PageEnum.PageHome + + property string defaultColor: "#1C1D21" + + property string borderColor: "#2C2D30" + + property string currentServerName: menuContent.currentItem.delegateData.desc + property string currentServerDescription: menuContent.currentItem.delegateData.address + + Rectangle { + id: buttonBackground + anchors.fill: buttonContent + anchors.bottomMargin: -radius + + radius: 16 + color: defaultColor + border.color: borderColor + border.width: 1 + + Rectangle { + width: parent.width + height: 1 + y: parent.height - height - parent.radius + + color: borderColor + } + } + + ColumnLayout { + id: buttonContent + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + + RowLayout { + Layout.topMargin: 24 + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + + Header1TextType { + text: currentServerName + } + + Image { + Layout.preferredWidth: 18 + Layout.preferredHeight: 18 + + source: "qrc:/images/controls/chevron-down.svg" + } + } + + LabelTextType { + Layout.bottomMargin: 44 + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + + text: currentServerDescription + } + } + + MouseArea { + anchors.fill: buttonBackground + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + + onClicked: { + menu.visible = true + } + } + + Drawer { + id: menu + + edge: Qt.BottomEdge + width: parent.width + height: parent.height * 0.90 + + clip: true + modal: true + + background: Rectangle { + anchors.fill: parent + anchors.bottomMargin: -radius + radius: 16 + + color: "#1C1D21" + border.color: borderColor + border.width: 1 + } + + Overlay.modal: Rectangle { + color: Qt.rgba(14/255, 14/255, 17/255, 0.8) + } + + ColumnLayout { + id: menuHeader + anchors.top: parent.top + anchors.right: parent.right + anchors.left: parent.left + + Header1TextType { + Layout.topMargin: 24 + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + + text: currentServerName + } + + LabelTextType { + Layout.bottomMargin: 24 + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + + text: currentServerDescription + } + + RowLayout { + + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + spacing: 8 + + DropDownType { + implicitHeight: 40 + + borderWidth: 0 + buttonImageColor: "#0E0E11" + + defaultColor: "#D7D8DB" + + textColor: "#0E0E11" + text: "testtesttest" + } + + BasicButtonType { + implicitHeight: 40 + + text: "Amnezia DNS" + } + } + + Header2Type { + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: "Серверы" + } + } + + +// Header2TextType { +// id: menuHeader +// width: parent.width + +// text: "Данные для подключения" +// wrapMode: Text.WordWrap + +// anchors.top: parent.top +// anchors.left: parent.left +// anchors.right: parent.right +// anchors.topMargin: 16 +// anchors.leftMargin: 16 +// anchors.rightMargin: 16 +// } + + FlickableType { + anchors.top: menuHeader.bottom + anchors.topMargin: 16 + contentHeight: col.implicitHeight + + Column { + id: col + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + spacing: 16 + + ButtonGroup { + id: radioButtonGroup + } + + ListView { + id: menuContent + width: parent.width + height: menuContent.contentItem.height + + model: ServersModel + currentIndex: 0 + + clip: true + interactive: false + + delegate: Item { + id: menuContentDelegate + + property variant delegateData: model + + implicitWidth: menuContent.width + implicitHeight: radioButton.implicitHeight + + RadioButton { + id: radioButton + + implicitWidth: parent.width + implicitHeight: radioButtonContent.implicitHeight + + hoverEnabled: true + + ButtonGroup.group: radioButtonGroup + + indicator: Rectangle { + anchors.fill: parent + color: radioButton.hovered ? "#2C2D30" : "#1C1D21" + } + + RowLayout { + id: radioButtonContent + anchors.fill: parent + + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + z: 1 + + Text { + id: text + + text: desc + color: "#D7D8DB" + font.pixelSize: 16 + font.weight: 400 + font.family: "PT Root UI VF" + + height: 24 + + Layout.fillWidth: true + Layout.topMargin: 20 + Layout.bottomMargin: 20 + } + + Image { + source: "qrc:/images/controls/check.svg" + visible: radioButton.checked + width: 24 + height: 24 + + Layout.rightMargin: 8 + } + } + } + } + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSettings.qml b/client/ui/qml/Pages2/PageSettings.qml new file mode 100644 index 00000000..5560aee7 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -0,0 +1,5 @@ +import QtQuick + +Item { + +} diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 26f25a15..a6ba52bb 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -64,7 +64,6 @@ PageBase { FileDialog { id: fileDialog -// currentFolder: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0] onAccepted: { } @@ -76,7 +75,7 @@ PageBase { color: "#2C2D30" } - //todo ifdef mobile platforms> + //todo ifdef mobile platforms LabelWithButtonType { Layout.fillWidth: true diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 9b6e788f..17c22b04 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -41,7 +41,7 @@ PageBase { descriptionText: "Эти настройки можно будет изменить позже" } - BodyTextType { + ParagraphTextType { Layout.topMargin: 16 text: "Network protocol" diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml new file mode 100644 index 00000000..0167ed1f --- /dev/null +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -0,0 +1,163 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "./" +import "../Pages" +import "../Controls2" +import "../Config" +import "../Controls2/TextTypes" + +PageBase { + id: root + page: PageEnum.PageSetupWizardStart + + FlickableType { + id: fl + 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 + + Image { + id: image + source: "qrc:/images/amneziaBigLogo.png" + + Layout.alignment: Qt.AlignCenter + Layout.topMargin: 32 + Layout.leftMargin: 8 + Layout.rightMargin: 8 + Layout.preferredWidth: 344 + Layout.preferredHeight: 279 + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 50 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: "Бесплатный сервис для создания личного VPN на вашем сервере. Помогаем получать доступ к заблокированному контенту, не раскрывая конфиденциальность даже провайдерам VPN." + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("У меня есть данные для подключения") + + onClicked: { + drawer.visible = true + } + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 8 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + 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("У меня ничего нет") + + onClicked: { + UiLogic.goToPage(PageEnum.PageTest) + } + } + } + + Drawer { + id: drawer + + edge: Qt.BottomEdge + width: parent.width + height: parent.height * 0.4375 + + clip: true + modal: true + + background: Rectangle { + anchors.fill: parent + anchors.bottomMargin: -radius + radius: 16 + color: "#1C1D21" + + border.color: borderColor + border.width: 1 + } + + Overlay.modal: Rectangle { + color: Qt.rgba(14/255, 14/255, 17/255, 0.8) + } + + ColumnLayout { + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + Header2TextType { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.alignment: Qt.AlignHCenter + + text: "Данные для подключения" + wrapMode: Text.WordWrap + } + + LabelWithButtonType { + id: ip + Layout.fillWidth: true + Layout.topMargin: 32 + + text: "IP, логин и пароль от сервера" + buttonImage: "qrc:/images/controls/chevron-right.svg" + + onClickedFunc: function() { + UiLogic.goToPage(PageEnum.PageSetupWizardCredentials) + drawer.visible = false + } + } + Rectangle { + Layout.fillWidth: true + height: 1 + color: "#2C2D30" + } + LabelWithButtonType { + Layout.fillWidth: true + + text: "QR-код, ключ или файл настроек" + buttonImage: "qrc:/images/controls/chevron-right.svg" + + onClickedFunc: function() { + UiLogic.goToPage(PageEnum.PageSetupWizardConfigSource) + drawer.visible = false + } + } + Rectangle { + Layout.fillWidth: true + height: 1 + color: "#2C2D30" + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml new file mode 100644 index 00000000..5560aee7 --- /dev/null +++ b/client/ui/qml/Pages2/PageShare.qml @@ -0,0 +1,5 @@ +import QtQuick + +Item { + +} diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index e04ebbb6..9e94ed28 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -7,154 +7,74 @@ import PageEnum 1.0 import "./" import "../Pages" import "../Controls2" -import "../Config" import "../Controls2/TextTypes" +import "../Config" PageBase { id: root page: PageEnum.PageStart - FlickableType { - id: fl - anchors.top: root.top - anchors.bottom: root.bottom - contentHeight: content.height + StackLayout { + id: stackLayout + currentIndex: tabBar.currentIndex - ColumnLayout { - id: content + anchors.top: parent.top + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: tabBar.top - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - Image { - id: image - source: "qrc:/images/amneziaBigLogo.png" - - Layout.alignment: Qt.AlignCenter - Layout.topMargin: 32 - Layout.leftMargin: 8 - Layout.rightMargin: 8 - Layout.preferredWidth: 344 - Layout.preferredHeight: 279 - } - - BodyTextType { - Layout.fillWidth: true - Layout.topMargin: 50 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - text: "Бесплатный сервис для создания личного VPN на вашем сервере. Помогаем получать доступ к заблокированному контенту, не раскрывая конфиденциальность даже провайдерам VPN." - } - - BasicButtonType { - Layout.fillWidth: true - Layout.topMargin: 32 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - text: qsTr("У меня есть данные для подключения") - - onClicked: { - drawer.visible = true - } - } - - BasicButtonType { - Layout.fillWidth: true - Layout.topMargin: 8 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - 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("У меня ничего нет") - - onClicked: { - UiLogic.goToPage(PageEnum.PageTest) - } - } + width: { + console.log(parent.width) + return parent.width + } + height: { + console.log(root.height - tabBar.implicitHeight) + return root.height - tabBar.implicitHeight } - Drawer { - id: drawer + PageHome { + } - edge: Qt.BottomEdge - width: parent.width - height: parent.height * 0.4375 - - clip: true - modal: true - - background: Rectangle { - anchors.fill: parent - anchors.bottomMargin: -radius - radius: 16 - color: "#1C1D21" - } - - Overlay.modal: Rectangle { - color: Qt.rgba(14/255, 14/255, 17/255, 0.8) - } - - ColumnLayout { - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - anchors.rightMargin: 16 - anchors.leftMargin: 16 - - Header2TextType { - Layout.fillWidth: true - Layout.topMargin: 24 - Layout.alignment: Qt.AlignHCenter - - text: "Данные для подключения" - wrapMode: Text.WordWrap - } - - LabelWithButtonType { - id: ip - Layout.fillWidth: true - Layout.topMargin: 32 - - text: "IP, логин и пароль от сервера" - buttonImage: "qrc:/images/controls/chevron-right.svg" - - onClickedFunc: function() { - UiLogic.goToPage(PageEnum.PageSetupWizardCredentials) - drawer.visible = false - } - } - Rectangle { - Layout.fillWidth: true - height: 1 - color: "#2C2D30" - } - LabelWithButtonType { - Layout.fillWidth: true - - text: "QR-код, ключ или файл настроек" - buttonImage: "qrc:/images/controls/chevron-right.svg" - - onClickedFunc: function() { - UiLogic.goToPage(PageEnum.PageSetupWizardConfigSource) - drawer.visible = false - } - } - Rectangle { - Layout.fillWidth: true - height: 1 - color: "#2C2D30" - } - } + PageSetupWizardEasy { } } + + TabBar { + id: tabBar + + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + + topPadding: 8 + bottomPadding: 34 + leftPadding: 96 + rightPadding: 96 + + background: Rectangle { + color: "#1C1D21" + } + + + TabImageButtonType { + isSelected: tabBar.currentIndex === 0 + image: "qrc:/images/controls/home.svg" + } + TabImageButtonType { + isSelected: tabBar.currentIndex === 1 + image: "qrc:/images/controls/share-2.svg" + } + TabImageButtonType { + isSelected: tabBar.currentIndex === 2 + image: "qrc:/images/controls/settings-2.svg" + } + } + + MouseArea { + anchors.fill: tabBar + anchors.leftMargin: 96 + anchors.rightMargin: 96 + cursorShape: Qt.PointingHandCursor + enabled: false + } } diff --git a/client/ui/uilogic.cpp b/client/ui/uilogic.cpp index 63371420..bb2f90b2 100644 --- a/client/ui/uilogic.cpp +++ b/client/ui/uilogic.cpp @@ -151,10 +151,10 @@ void UiLogic::initializeUiLogic() if (m_settings->serversCount() > 0) { if (m_settings->defaultServerIndex() < 0) m_settings->setDefaultServer(0); - emit goToPage(Page::Vpn, true, false); + emit goToPage(Page::PageStart, true, false); } else { - emit goToPage(Page::PageStart, true, false); + emit goToPage(Page::PageSetupWizardStart, true, false); } m_selectedServerIndex = m_settings->defaultServerIndex(); From b66f4bf2bee3a31fe1cbb7036a33893127a90686 Mon Sep 17 00:00:00 2001 From: Vladimir Kuznetsov Date: Thu, 11 May 2023 14:50:50 +0800 Subject: [PATCH 014/278] added display of protocols on PageHome --- client/amnezia_application.cpp | 1 - client/images/controls/plus.svg | 4 + client/resources.qrc | 1 + client/ui/models/servers_model.cpp | 50 ++++--- client/ui/models/servers_model.h | 5 +- client/ui/pages_logic/ServerListLogic.cpp | 2 +- client/ui/qml/Controls2/BasicButtonType.qml | 2 +- client/ui/qml/Controls2/DropDownType.qml | 80 +++-------- client/ui/qml/Controls2/Header2Type.qml | 38 +++++- client/ui/qml/Controls2/HeaderType.qml | 42 +++++- .../ui/qml/Controls2/LabelWithButtonType.qml | 4 +- client/ui/qml/Controls2/TabButtonType.qml | 2 +- .../ui/qml/Controls2/VerticalRadioButton.qml | 2 +- client/ui/qml/Pages2/PageHome.qml | 126 +++++++++++++++--- .../Pages2/PageSetupWizardConfigSource.qml | 2 +- .../qml/Pages2/PageSetupWizardCredentials.qml | 2 +- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 2 +- .../qml/Pages2/PageSetupWizardInstalling.qml | 2 +- .../PageSetupWizardProtocolSettings.qml | 2 +- .../qml/Pages2/PageSetupWizardProtocols.qml | 2 +- .../ui/qml/Pages2/PageSetupWizardTextKey.qml | 2 +- client/ui/qml/Pages2/PageTest.qml | 2 +- 22 files changed, 237 insertions(+), 138 deletions(-) create mode 100644 client/images/controls/plus.svg diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 2cd61679..dea868e4 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -187,7 +187,6 @@ void AmneziaApplication::loadFonts() QFontDatabase::addApplicationFont(":/fonts/Lato-Regular.ttf"); QFontDatabase::addApplicationFont(":/fonts/Lato-Thin.ttf"); QFontDatabase::addApplicationFont(":/fonts/Lato-ThinItalic.ttf"); - QFontDatabase::addApplicationFont(":/fonts/pt-root-ui_vf.ttf"); } diff --git a/client/images/controls/plus.svg b/client/images/controls/plus.svg new file mode 100644 index 00000000..96d0b71d --- /dev/null +++ b/client/images/controls/plus.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/resources.qrc b/client/resources.qrc index f5817eb0..307c992d 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -221,5 +221,6 @@ ui/qml/Controls2/TextTypes/LabelTextType.qml ui/qml/Controls2/TextTypes/ButtonTextType.qml ui/qml/Controls2/Header2Type.qml + images/controls/plus.svg diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 94d3ced4..89287b7f 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -2,41 +2,34 @@ ServersModel::ServersModel(std::shared_ptr settings, QObject *parent) : m_settings(settings), QAbstractListModel(parent) { + refresh(); +} + +void ServersModel::refresh() +{ + beginResetModel(); const QJsonArray &servers = m_settings->serversArray(); int defaultServer = m_settings->defaultServerIndex(); QVector serverListContent; for(int i = 0; i < servers.size(); i++) { - ServerModelContent c; + ServerModelContent content; auto server = servers.at(i).toObject(); - c.desc = server.value(config_key::description).toString(); - c.address = server.value(config_key::hostName).toString(); - if (c.desc.isEmpty()) { - c.desc = c.address; + content.desc = server.value(config_key::description).toString(); + content.address = server.value(config_key::hostName).toString(); + if (content.desc.isEmpty()) { + content.desc = content.address; } - c.isDefault = (i == defaultServer); - serverListContent.push_back(c); + content.isDefault = (i == defaultServer); + serverListContent.push_back(content); } - setContent(serverListContent); -} - -void ServersModel::clearData() -{ - beginResetModel(); - m_content.clear(); - endResetModel(); -} - -void ServersModel::setContent(const QVector &data) -{ - beginResetModel(); - m_content = data; + m_data = serverListContent; endResetModel(); } int ServersModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); - return static_cast(m_content.size()); + return static_cast(m_data.size()); } QHash ServersModel::roleNames() const { @@ -50,19 +43,24 @@ QHash ServersModel::roleNames() const { QVariant ServersModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() < 0 - || index.row() >= static_cast(m_content.size())) { + || index.row() >= static_cast(m_data.size())) { return QVariant(); } if (role == DescRole) { - return m_content[index.row()].desc; + return m_data[index.row()].desc; } if (role == AddressRole) { - return m_content[index.row()].address; + return m_data[index.row()].address; } if (role == IsDefaultRole) { - return m_content[index.row()].isDefault; + return m_data[index.row()].isDefault; } return QVariant(); } +void ServersModel::setDefaultServerIndex(int index) +{ + +} + diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index e60aea6b..c8c32b56 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -23,8 +23,7 @@ public: IsDefaultRole }; - void clearData(); - void setContent(const QVector& data); + void refresh(); int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; @@ -33,7 +32,7 @@ protected: QHash roleNames() const override; private: - QVector m_content; + QVector m_data; std::shared_ptr m_settings; }; diff --git a/client/ui/pages_logic/ServerListLogic.cpp b/client/ui/pages_logic/ServerListLogic.cpp index 4a17e202..56a682b8 100644 --- a/client/ui/pages_logic/ServerListLogic.cpp +++ b/client/ui/pages_logic/ServerListLogic.cpp @@ -45,5 +45,5 @@ void ServerListLogic::onUpdatePage() c.isDefault = (i == defaultServer); serverListContent.push_back(c); } - qobject_cast(m_serverListModel)->setContent(serverListContent); +// qobject_cast(m_serverListModel)->setContent(serverListContent); } diff --git a/client/ui/qml/Controls2/BasicButtonType.qml b/client/ui/qml/Controls2/BasicButtonType.qml index da5d2abf..266beefe 100644 --- a/client/ui/qml/Controls2/BasicButtonType.qml +++ b/client/ui/qml/Controls2/BasicButtonType.qml @@ -48,7 +48,7 @@ Button { contentItem: Text { anchors.fill: background - font.family: "PT Root UI" + font.family: "PT Root UI VF" font.styleName: "normal" font.weight: 400 font.pixelSize: 16 diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 6f511194..ea6ca5fc 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -10,6 +10,9 @@ Item { property string text property string descriptionText + property string headerText + property string headerBackButtonImage + property var onClickedFunc property string buttonImage: "qrc:/images/controls/chevron-down.svg" property string buttonImageColor: "#494B50" @@ -22,7 +25,10 @@ Item { property string borderColor: "#494B50" property int borderWidth: 1 - property alias menuModel: menuContent.model + property Component menuDelegate + property variant menuModel + + property alias menuVisible: menu.visible implicitWidth: buttonContent.implicitWidth implicitHeight: buttonContent.implicitHeight @@ -128,12 +134,13 @@ Item { color: Qt.rgba(14/255, 14/255, 17/255, 0.8) } - Header2TextType { + Header2Type { id: header - width: parent.width - text: "Данные для подключения" - wrapMode: Text.WordWrap + headerText: root.headerText + backButtonImage: root.headerBackButtonImage + + width: parent.width anchors.top: parent.top anchors.left: parent.left @@ -170,64 +177,13 @@ Item { clip: true interactive: false - delegate: Item { - implicitWidth: menuContent.width - implicitHeight: radioButton.implicitHeight + model: root.menuModel - RadioButton { - id: radioButton - - implicitWidth: parent.width - implicitHeight: radioButtonContent.implicitHeight - - hoverEnabled: true - - ButtonGroup.group: radioButtonGroup - - indicator: Rectangle { - anchors.fill: parent - color: radioButton.hovered ? "#2C2D30" : "#1C1D21" - } - - RowLayout { - id: radioButtonContent - anchors.fill: parent - - anchors.rightMargin: 16 - anchors.leftMargin: 16 - - z: 1 - - Text { - id: text - - text: modelData - color: "#D7D8DB" - font.pixelSize: 16 - font.weight: 400 - font.family: "PT Root UI VF" - - height: 24 - - Layout.fillWidth: true - Layout.topMargin: 20 - Layout.bottomMargin: 20 - } - - Image { - source: "qrc:/images/controls/check.svg" - visible: radioButton.checked - width: 24 - height: 24 - - Layout.rightMargin: 8 - } - } - - onClicked: { - root.text = modelData - menu.visible = false - } + delegate: Row { + Loader { + id: loader + sourceComponent: root.menuDelegate + property QtObject modelData: model } } } diff --git a/client/ui/qml/Controls2/Header2Type.qml b/client/ui/qml/Controls2/Header2Type.qml index e0173a73..5c2f0c9b 100644 --- a/client/ui/qml/Controls2/Header2Type.qml +++ b/client/ui/qml/Controls2/Header2Type.qml @@ -6,7 +6,12 @@ import "TextTypes" Item { id: root - property string buttonImage + property string backButtonImage + property string actionButtonImage + + property var backButtonFunction + property var actionButtonFunction + property string headerText property string descriptionText @@ -22,22 +27,41 @@ Item { Layout.leftMargin: -6 - image: root.buttonImage + image: root.backButtonImage imageColor: "#D7D8DB" visible: image ? true : false onClicked: { - UiLogic.closePage() + if (backButtonFunction && typeof backButtonFunction === "function") { + backButtonFunction() + } } } - Header2TextType { - id: header + RowLayout { + Header2TextType { + id: header - Layout.fillWidth: true + Layout.fillWidth: true - text: root.headerText + text: root.headerText + } + + ImageButtonType { + id: headerActionButton + + image: root.actionButtonImage + imageColor: "#D7D8DB" + + visible: image ? true : false + + onClicked: { + if (actionButtonImage && typeof actionButtonImage === "function") { + actionButtonImage() + } + } + } } ParagraphTextType { diff --git a/client/ui/qml/Controls2/HeaderType.qml b/client/ui/qml/Controls2/HeaderType.qml index 407f67f0..a051d2d8 100644 --- a/client/ui/qml/Controls2/HeaderType.qml +++ b/client/ui/qml/Controls2/HeaderType.qml @@ -6,7 +6,12 @@ import "TextTypes" Item { id: root - property string buttonImage + property string backButtonImage + property string actionButtonImage + + property var backButtonFunction + property var actionButtonFunction + property string headerText property string descriptionText @@ -22,22 +27,43 @@ Item { Layout.leftMargin: -6 - image: root.buttonImage + image: root.backButtonImage imageColor: "#D7D8DB" visible: image ? true : false onClicked: { - UiLogic.closePage() + if (backButtonFunction && typeof backButtonFunction === "function") { + backButtonFunction() + } } } - Header1TextType { - id: header + RowLayout { + Header1TextType { + id: header - Layout.fillWidth: true + Layout.fillWidth: true - text: root.headerText + text: root.headerText + } + + ImageButtonType { + id: headerActionButton + + Layout.alignment: Qt.AlignRight + + image: root.actionButtonImage + imageColor: "#D7D8DB" + + visible: image ? true : false + + onClicked: { + if (actionButtonImage && typeof actionButtonImage === "function") { + actionButtonImage() + } + } + } } ParagraphTextType { @@ -49,6 +75,8 @@ Item { text: root.descriptionText color: "#878B91" + + visible: root.descriptionText !== "" } } } diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml index b6d113bb..d8e195f1 100644 --- a/client/ui/qml/Controls2/LabelWithButtonType.qml +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -29,7 +29,7 @@ Item { ColumnLayout { Text { - font.family: "PT Root UI" + font.family: "PT Root UI VF" font.styleName: "normal" font.pixelSize: 18 color: "#d7d8db" @@ -44,7 +44,7 @@ Item { } Text { - font.family: "PT Root UI" + font.family: "PT Root UI VF" font.styleName: "normal" font.pixelSize: 13 font.letterSpacing: 0.02 diff --git a/client/ui/qml/Controls2/TabButtonType.qml b/client/ui/qml/Controls2/TabButtonType.qml index f39edefd..4699dfcd 100644 --- a/client/ui/qml/Controls2/TabButtonType.qml +++ b/client/ui/qml/Controls2/TabButtonType.qml @@ -43,7 +43,7 @@ TabButton { anchors.fill: background height: 24 - font.family: "PT Root UI" + font.family: "PT Root UI VF" font.styleName: "normal" font.weight: 500 font.pixelSize: 16 diff --git a/client/ui/qml/Controls2/VerticalRadioButton.qml b/client/ui/qml/Controls2/VerticalRadioButton.qml index edcd1c29..420051cd 100644 --- a/client/ui/qml/Controls2/VerticalRadioButton.qml +++ b/client/ui/qml/Controls2/VerticalRadioButton.qml @@ -136,7 +136,7 @@ RadioButton { } Text { - font.family: "PT Root UI" + font.family: "PT Root UI VF" font.styleName: "normal" font.pixelSize: 13 font.letterSpacing: 0.02 diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index ad1b7d38..3f81f168 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -2,7 +2,10 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import SortFilterProxyModel 0.2 + import PageEnum 1.0 +import ProtocolEnum 1.0 import "./" import "../Pages" @@ -125,11 +128,27 @@ PageBase { } RowLayout { - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter spacing: 8 + SortFilterProxyModel { + id: proxyContainersModel + sourceModel: ContainersModel + filters: [ + ValueFilter { + roleName: "service_type_role" + value: ProtocolEnum.Vpn + }, + ValueFilter { + roleName: "is_installed_role" + value: true + } + ] + } + DropDownType { + id: protocolsDropDown + implicitHeight: 40 borderWidth: 0 @@ -138,7 +157,83 @@ PageBase { defaultColor: "#D7D8DB" textColor: "#0E0E11" - text: "testtesttest" + headerText: "Протокол подключения" + headerBackButtonImage: "qrc:/images/controls/arrow-left.svg" + + menuModel: proxyContainersModel + + menuDelegate: Item { + implicitWidth: menuContent.width + implicitHeight: radioButton.implicitHeight + + RadioButton { + id: radioButton + + implicitWidth: parent.width + implicitHeight: radioButtonContent.implicitHeight + + hoverEnabled: true + + ButtonGroup.group: radioButtonGroup + + indicator: Rectangle { + anchors.fill: parent + color: radioButton.hovered ? "#2C2D30" : "#1C1D21" + } + + RowLayout { + id: radioButtonContent + anchors.fill: parent + + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + z: 1 + + Text { + id: text + + // todo remove dirty hack? + text: { + if (modelData !== null) { + return modelData.name_role + } else + return "" + } + color: "#D7D8DB" + font.pixelSize: 16 + font.weight: 400 + font.family: "PT Root UI VF" + + height: 24 + + Layout.fillWidth: true + Layout.topMargin: 20 + Layout.bottomMargin: 20 + } + + Image { + source: "qrc:/images/controls/check.svg" + visible: radioButton.checked + width: 24 + height: 24 + + Layout.rightMargin: 8 + } + } + + onClicked: { + protocolsDropDown.text = text.text + protocolsDropDown.menuVisible = false + } + } + + Component.onCompleted: { + if (modelData !== null && modelData.default_role) { + protocolsDropDown.text = modelData.name_role + } + } + } } BasicButtonType { @@ -149,29 +244,17 @@ PageBase { } Header2Type { + Layout.fillWidth: true + Layout.topMargin: 48 Layout.leftMargin: 16 Layout.rightMargin: 16 + actionButtonImage: "qrc:/images/controls/plus.svg" + headerText: "Серверы" } } - -// Header2TextType { -// id: menuHeader -// width: parent.width - -// text: "Данные для подключения" -// wrapMode: Text.WordWrap - -// anchors.top: parent.top -// anchors.left: parent.left -// anchors.right: parent.right -// anchors.topMargin: 16 -// anchors.leftMargin: 16 -// anchors.rightMargin: 16 -// } - FlickableType { anchors.top: menuHeader.bottom anchors.topMargin: 16 @@ -257,6 +340,13 @@ PageBase { Layout.rightMargin: 8 } } + + onClicked: { + console.log(index) + ContainersModel.setSelectedServerIndex(index) + root.currentServerName = desc + root.currentServerDescription = address + } } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index a6ba52bb..5445d1f3 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -36,7 +36,7 @@ PageBase { Layout.fillWidth: true Layout.topMargin: 20 - buttonImage: "qrc:/images/controls/arrow-left.svg" + backButtonImage: "qrc:/images/controls/arrow-left.svg" headerText: "Подключение к серверу" descriptionText: "Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватывать ваши данные.\n diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index a6e98fee..506c1cde 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -34,7 +34,7 @@ PageBase { Layout.fillWidth: true Layout.topMargin: 20 - buttonImage: "qrc:/images/controls/arrow-left.svg" + backButtonImage: "qrc:/images/controls/arrow-left.svg" headerText: "Подключение к серверу" } diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index 8c75a1df..94e22fe3 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -34,7 +34,7 @@ PageBase { Layout.fillWidth: true Layout.topMargin: 20 - buttonImage: "qrc:/images/controls/arrow-left.svg" + backButtonImage: "qrc:/images/controls/arrow-left.svg" headerText: "Какой уровень контроля интернета в вашем регионе?" } diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index 3445b533..a7418257 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -36,7 +36,7 @@ PageBase { Layout.topMargin: 20 //TODO remove later - buttonImage: "qrc:/images/controls/arrow-left.svg" + backButtonImage: "qrc:/images/controls/arrow-left.svg" headerText: "Установка" descriptionText: ContainersModel.getCurrentlyInstalledContainerName() diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 17c22b04..78856444 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -35,7 +35,7 @@ PageBase { Layout.fillWidth: true Layout.topMargin: 20 - buttonImage: "qrc:/images/controls/arrow-left.svg" + backButtonImage: "qrc:/images/controls/arrow-left.svg" headerText: "Установка " + ContainersModel.getCurrentlyInstalledContainerName() descriptionText: "Эти настройки можно будет изменить позже" diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index ad4efbc2..50107615 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -51,7 +51,7 @@ PageBase { HeaderType { width: parent.width - buttonImage: "qrc:/images/controls/arrow-left.svg" + backButtonImage: "qrc:/images/controls/arrow-left.svg" headerText: "Протокол подключения" descriptionText: "Выберите более приоритетный для вас. Позже можно будет установить остальные протоколы и доп сервисы, вроде DNS-прокси и SFTP." diff --git a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml index 9fddc573..d295f1b9 100644 --- a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml +++ b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml @@ -35,7 +35,7 @@ PageBase { Layout.fillWidth: true Layout.topMargin: 20 - buttonImage: "qrc:/images/controls/arrow-left.svg" + backButtonImage: "qrc:/images/controls/arrow-left.svg" headerText: "Ключ для подключения" descriptionText: "Строка, которая начинается с vpn://..." diff --git a/client/ui/qml/Pages2/PageTest.qml b/client/ui/qml/Pages2/PageTest.qml index af9ef286..2336e46e 100644 --- a/client/ui/qml/Pages2/PageTest.qml +++ b/client/ui/qml/Pages2/PageTest.qml @@ -29,7 +29,7 @@ PageBase { Layout.bottomMargin: 32 Layout.fillWidth: true - buttonImage: "qrc:/images/controls/arrow-left.svg" + backButtonImage: "qrc:/images/controls/arrow-left.svg" headerText: "Server 1" descriptionText: "root 192.168.111.111" } From e3e7503a7c00afeaa4d4b8d8f6d9494bf30440db Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 12 May 2023 11:36:09 +0800 Subject: [PATCH 015/278] added saving the selected server and protocol to the config --- client/ui/models/containers_model.cpp | 37 ++++--- client/ui/models/containers_model.h | 1 + client/ui/models/servers_model.cpp | 118 ++++++++++++++--------- client/ui/models/servers_model.h | 13 ++- client/ui/qml/Controls2/DropDownType.qml | 6 ++ client/ui/qml/Pages2/PageHome.qml | 82 +++++++++------- 6 files changed, 160 insertions(+), 97 deletions(-) diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index f56613c7..c7aa0ec1 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -1,10 +1,8 @@ #include "containers_model.h" -ContainersModel::ContainersModel(std::shared_ptr settings, QObject *parent) : - m_settings(settings), - QAbstractListModel(parent) +ContainersModel::ContainersModel(std::shared_ptr settings, QObject *parent) : m_settings(settings), QAbstractListModel(parent) { - + setSelectedServerIndex(m_settings->defaultServerIndex()); } int ContainersModel::rowCount(const QModelIndex &parent) const @@ -13,14 +11,19 @@ int ContainersModel::rowCount(const QModelIndex &parent) const return ContainerProps::allContainers().size(); } -QHash ContainersModel::roleNames() const { - QHash roles; - roles[NameRole] = "name_role"; - roles[DescRole] = "desc_role"; - roles[DefaultRole] = "default_role"; - roles[ServiceTypeRole] = "service_type_role"; - roles[IsInstalledRole] = "is_installed_role"; - return roles; +bool ContainersModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { + return false; + } + + if (role == DefaultRole) { + DockerContainer container = ContainerProps::allContainers().at(index.row()); + m_settings->setDefaultContainer(m_selectedServerIndex, container); + } + + emit dataChanged(index, index); + return true; } QVariant ContainersModel::data(const QModelIndex &index, int role) const @@ -67,3 +70,13 @@ QString ContainersModel::getCurrentlyInstalledContainerName() { return data(m_currentlyInstalledContainerIndex, NameRole).toString(); } + +QHash ContainersModel::roleNames() const { + QHash roles; + roles[NameRole] = "name_role"; + roles[DescRole] = "desc_role"; + roles[DefaultRole] = "default_role"; + roles[ServiceTypeRole] = "service_type_role"; + roles[IsInstalledRole] = "is_installed_role"; + return roles; +} diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index 9c409fc0..ba0fea57 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -25,6 +25,7 @@ public: int rowCount(const QModelIndex &parent = QModelIndex()) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; Q_INVOKABLE void setSelectedServerIndex(int index); Q_INVOKABLE void setCurrentlyInstalledContainerIndex(int index); diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 89287b7f..44ed2619 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -2,34 +2,83 @@ ServersModel::ServersModel(std::shared_ptr settings, QObject *parent) : m_settings(settings), QAbstractListModel(parent) { - refresh(); -} -void ServersModel::refresh() -{ - beginResetModel(); - const QJsonArray &servers = m_settings->serversArray(); - int defaultServer = m_settings->defaultServerIndex(); - QVector serverListContent; - for(int i = 0; i < servers.size(); i++) { - ServerModelContent content; - auto server = servers.at(i).toObject(); - content.desc = server.value(config_key::description).toString(); - content.address = server.value(config_key::hostName).toString(); - if (content.desc.isEmpty()) { - content.desc = content.address; - } - content.isDefault = (i == defaultServer); - serverListContent.push_back(content); - } - m_data = serverListContent; - endResetModel(); } int ServersModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); - return static_cast(m_data.size()); + return static_cast(m_settings->serversCount()); +} + +bool ServersModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || index.row() < 0 + || index.row() >= static_cast(m_settings->serversCount())) { + return false; + } +// if (role == DescRole) { +// return m_data[index.row()].desc; +// } +// if (role == AddressRole) { +// return m_data[index.row()].address; +// } +// if (role == IsDefaultRole) { +// return m_data[index.row()].isDefault; +// } +} + +QVariant ServersModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(m_settings->serversCount())) { + return QVariant(); + } + + const QJsonArray &servers = m_settings->serversArray(); + const QJsonObject server = servers.at(index.row()).toObject(); + + if (role == DescRole) { + auto description = server.value(config_key::description).toString(); + if (description.isEmpty()) { + return server.value(config_key::hostName).toString(); + } + return description; + } + if (role == AddressRole) { + return server.value(config_key::hostName).toString(); + } + if (role == IsDefaultRole) { + return index.row() == m_settings->defaultServerIndex(); + } + return QVariant(); + + +// if (!index.isValid() || index.row() < 0 +// || index.row() >= static_cast(m_data.size())) { +// return QVariant(); +// } +// if (role == DescRole) { +// return m_data[index.row()].desc; +// } +// if (role == AddressRole) { +// return m_data[index.row()].address; +// } +// if (role == IsDefaultRole) { +// return m_data[index.row()].isDefault; +// } +// return QVariant(); +} + +void ServersModel::setDefaultServerIndex(int index) +{ +// beginResetModel(); + m_settings->setDefaultServer(index); + // endResetModel(); +} + +int ServersModel::getDefaultServerIndex() +{ + return m_settings->defaultServerIndex(); } QHash ServersModel::roleNames() const { @@ -39,28 +88,3 @@ QHash ServersModel::roleNames() const { roles[IsDefaultRole] = "is_default"; return roles; } - -QVariant ServersModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid() || index.row() < 0 - || index.row() >= static_cast(m_data.size())) { - return QVariant(); - } - if (role == DescRole) { - return m_data[index.row()].desc; - } - if (role == AddressRole) { - return m_data[index.row()].address; - } - if (role == IsDefaultRole) { - return m_data[index.row()].isDefault; - } - return QVariant(); -} - -void ServersModel::setDefaultServerIndex(int index) -{ - -} - - diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index c8c32b56..cd46846b 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -15,24 +15,27 @@ class ServersModel : public QAbstractListModel { Q_OBJECT public: - ServersModel(std::shared_ptr settings, QObject *parent = nullptr); -public: - enum SiteRoles { + enum ServersModelRoles { DescRole = Qt::UserRole + 1, AddressRole, IsDefaultRole }; - void refresh(); + ServersModel(std::shared_ptr settings, QObject *parent = nullptr); + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; +public slots: + void setDefaultServerIndex(int index); + int getDefaultServerIndex(); + protected: QHash roleNames() const override; private: - QVector m_data; std::shared_ptr m_settings; }; diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index ea6ca5fc..64013ba4 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -17,6 +17,7 @@ Item { property string buttonImage: "qrc:/images/controls/chevron-down.svg" property string buttonImageColor: "#494B50" + property int buttonMaximumWidth property string defaultColor: "#1C1D21" @@ -70,8 +71,13 @@ Item { horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter + Layout.maximumWidth: buttonMaximumWidth ? buttonMaximumWidth : implicitWidth + color: root.textColor text: root.text + + wrapMode: Text.NoWrap + elide: Text.ElideRight } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 3f81f168..f4f4d13c 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -21,8 +21,8 @@ PageBase { property string borderColor: "#2C2D30" - property string currentServerName: menuContent.currentItem.delegateData.desc - property string currentServerDescription: menuContent.currentItem.delegateData.address + property string currentServerName: serversMenuContent.currentItem.delegateData.desc + property string currentServerDescription: serversMenuContent.currentItem.delegateData.address Rectangle { id: buttonBackground @@ -108,7 +108,7 @@ PageBase { } ColumnLayout { - id: menuHeader + id: serversMenuHeader anchors.top: parent.top anchors.right: parent.right anchors.left: parent.left @@ -147,12 +147,13 @@ PageBase { } DropDownType { - id: protocolsDropDown + id: containersDropDown implicitHeight: 40 borderWidth: 0 buttonImageColor: "#0E0E11" + buttonMaximumWidth: 150 defaultColor: "#D7D8DB" @@ -162,27 +163,38 @@ PageBase { menuModel: proxyContainersModel + ButtonGroup { + id: containersRadioButtonGroup + } + menuDelegate: Item { - implicitWidth: menuContent.width - implicitHeight: radioButton.implicitHeight + implicitWidth: root.width + implicitHeight: containerRadioButton.implicitHeight RadioButton { - id: radioButton + id: containerRadioButton implicitWidth: parent.width - implicitHeight: radioButtonContent.implicitHeight + implicitHeight: containerRadioButtonContent.implicitHeight hoverEnabled: true - ButtonGroup.group: radioButtonGroup + ButtonGroup.group: containersRadioButtonGroup + + checked: { + if (modelData !== null) { + return modelData.default_role + } + return false + } indicator: Rectangle { anchors.fill: parent - color: radioButton.hovered ? "#2C2D30" : "#1C1D21" + color: containerRadioButton.hovered ? "#2C2D30" : "#1C1D21" } RowLayout { - id: radioButtonContent + id: containerRadioButtonContent anchors.fill: parent anchors.rightMargin: 16 @@ -191,7 +203,7 @@ PageBase { z: 1 Text { - id: text + id: containerRadioButtonText // todo remove dirty hack? text: { @@ -214,7 +226,7 @@ PageBase { Image { source: "qrc:/images/controls/check.svg" - visible: radioButton.checked + visible: containerRadioButton.checked width: 24 height: 24 @@ -223,14 +235,16 @@ PageBase { } onClicked: { - protocolsDropDown.text = text.text - protocolsDropDown.menuVisible = false + modelData.default_role = true + + containersDropDown.text = containerRadioButtonText.text + containersDropDown.menuVisible = false } } Component.onCompleted: { if (modelData !== null && modelData.default_role) { - protocolsDropDown.text = modelData.name_role + containersDropDown.text = modelData.name_role } } } @@ -256,7 +270,7 @@ PageBase { } FlickableType { - anchors.top: menuHeader.bottom + anchors.top: serversMenuHeader.bottom anchors.topMargin: 16 contentHeight: col.implicitHeight @@ -269,45 +283,46 @@ PageBase { spacing: 16 ButtonGroup { - id: radioButtonGroup + id: serversRadioButtonGroup } ListView { - id: menuContent + id: serversMenuContent width: parent.width - height: menuContent.contentItem.height + height: serversMenuContent.contentItem.height model: ServersModel - currentIndex: 0 + currentIndex: ServersModel.getDefaultServerIndex() clip: true - interactive: false delegate: Item { id: menuContentDelegate property variant delegateData: model - implicitWidth: menuContent.width - implicitHeight: radioButton.implicitHeight + implicitWidth: serversMenuContent.width + implicitHeight: serverRadioButton.implicitHeight RadioButton { - id: radioButton + id: serverRadioButton implicitWidth: parent.width - implicitHeight: radioButtonContent.implicitHeight + implicitHeight: serverRadioButtonContent.implicitHeight hoverEnabled: true - ButtonGroup.group: radioButtonGroup + checked: index === serversMenuContent.currentIndex + + ButtonGroup.group: serversRadioButtonGroup indicator: Rectangle { anchors.fill: parent - color: radioButton.hovered ? "#2C2D30" : "#1C1D21" + color: serverRadioButton.hovered ? "#2C2D30" : "#1C1D21" } RowLayout { - id: radioButtonContent + id: serverRadioButtonContent anchors.fill: parent anchors.rightMargin: 16 @@ -316,7 +331,7 @@ PageBase { z: 1 Text { - id: text + id: serverRadioButtonText text: desc color: "#D7D8DB" @@ -333,7 +348,7 @@ PageBase { Image { source: "qrc:/images/controls/check.svg" - visible: radioButton.checked + visible: serverRadioButton.checked width: 24 height: 24 @@ -342,10 +357,11 @@ PageBase { } onClicked: { - console.log(index) - ContainersModel.setSelectedServerIndex(index) root.currentServerName = desc root.currentServerDescription = address + + ServersModel.setDefaultServerIndex(index) + ContainersModel.setSelectedServerIndex(index) } } } From 35d4222c7a87f6374fe324514ef0018cc96fe92a Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 12 May 2023 23:54:31 +0800 Subject: [PATCH 016/278] added connectionController and ConnectionButton.qml --- client/CMakeLists.txt | 5 ++ client/amnezia_application.cpp | 12 +++- client/amnezia_application.h | 10 ++- client/resources.qrc | 1 + .../ui/controllers/connectionController.cpp | 71 +++++++++++++++++++ client/ui/controllers/connectionController.h | 33 +++++++++ client/ui/models/containers_model.cpp | 48 +++++++------ client/ui/models/containers_model.h | 10 ++- client/ui/models/servers_model.cpp | 27 ++----- client/ui/models/servers_model.h | 1 + client/ui/qml/Components/ConnectButton.qml | 40 +++++++++++ .../Controls2/TextTypes/ButtonTextType.qml | 2 +- .../Controls2/TextTypes/Header1TextType.qml | 2 +- .../Controls2/TextTypes/Header2TextType.qml | 2 +- .../qml/Controls2/TextTypes/LabelTextType.qml | 2 +- .../Controls2/TextTypes/ParagraphTextType.qml | 2 +- client/ui/qml/Pages2/PageHome.qml | 25 ++++--- 17 files changed, 233 insertions(+), 60 deletions(-) create mode 100644 client/ui/controllers/connectionController.cpp create mode 100644 client/ui/controllers/connectionController.h create mode 100644 client/ui/qml/Components/ConnectButton.qml diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 9b110666..d86431d5 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -128,17 +128,22 @@ file(GLOB CONFIGURATORS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/configur file(GLOB UI_MODELS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/models/*.h) file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/models/*.cpp) +file(GLOB UI_CONTROLLERS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.h) +file(GLOB UI_CONTROLLERS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.cpp) + set(HEADERS ${HEADERS} ${COMMON_FILES_H} ${PAGE_LOGIC_H} ${CONFIGURATORS_H} ${UI_MODELS_H} + ${UI_CONTROLLERS_H} ) set(SOURCES ${SOURCES} ${COMMON_FILES_CPP} ${PAGE_LOGIC_CPP} ${CONFIGURATORS_CPP} ${UI_MODELS_CPP} + ${UI_CONTROLLERS_CPP} ) qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index dea868e4..6fadcabb 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -5,7 +5,6 @@ #include #include - #include "core/servercontroller.h" #include "logger.h" #include "defines.h" @@ -100,12 +99,23 @@ void AmneziaApplication::init() m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance()); + // m_containersModel.reset(new ContainersModel(m_settings, this)); m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get()); m_serversModel.reset(new ServersModel(m_settings, this)); m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get()); + m_vpnConnection.reset(new VpnConnection(m_settings, m_configurator)); + + m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel)); + connect(m_connectionController.get(), &ConnectionController::connectToVpn, + m_vpnConnection.get(), &VpnConnection::connectToVpn, Qt::QueuedConnection); + connect(m_connectionController.get(), &ConnectionController::disconnectFromVpn, + m_vpnConnection.get(), &VpnConnection::disconnectFromVpn, Qt::QueuedConnection); + m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get()); + + // m_uiLogic->registerPagesLogic(); #if defined(Q_OS_IOS) diff --git a/client/amnezia_application.h b/client/amnezia_application.h index d15864e1..322a440c 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -9,11 +9,13 @@ #include #include "settings.h" +#include "vpnconnection.h" #include "ui/uilogic.h" #include "configurators/vpn_configurator.h" #include "ui/models/servers_model.h" #include "ui/models/containers_model.h" +#include "ui/controllers/connectionController.h" #define amnApp (static_cast(QCoreApplication::instance())) @@ -58,8 +60,12 @@ private: QTranslator* m_translator; QCommandLineParser m_parser; - QScopedPointer m_containersModel; - QScopedPointer m_serversModel; + QSharedPointer m_containersModel; + QSharedPointer m_serversModel; + + QScopedPointer m_vpnConnection; + + QScopedPointer m_connectionController; }; diff --git a/client/resources.qrc b/client/resources.qrc index 307c992d..1ea7bf38 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -222,5 +222,6 @@ ui/qml/Controls2/TextTypes/ButtonTextType.qml ui/qml/Controls2/Header2Type.qml images/controls/plus.svg + ui/qml/Components/ConnectButton.qml diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp new file mode 100644 index 00000000..41dbed64 --- /dev/null +++ b/client/ui/controllers/connectionController.cpp @@ -0,0 +1,71 @@ +#include "connectionController.h" + +#include + +ConnectionController::ConnectionController(const QSharedPointer &serversModel, + const QSharedPointer &containersModel, + QObject *parent) : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel) +{ + +} + +bool ConnectionController::onConnectionButtonClicked() +{ + if (!isConnected) { + openVpnConnection(); + } else { + closeVpnConnection(); + } +} + +bool ConnectionController::openVpnConnection() +{ + int serverIndex = m_serversModel->getDefaultServerIndex(); + QModelIndex serverModelIndex = m_serversModel->index(serverIndex); + ServerCredentials credentials = qvariant_cast(m_serversModel->data(serverModelIndex, + ServersModel::ServersModelRoles::CredentialsRole)); + + DockerContainer container = m_containersModel->getDefaultContainer(); + QModelIndex containerModelIndex = m_containersModel->index(container); + const QJsonObject &containerConfig = qvariant_cast(m_containersModel->data(containerModelIndex, + ContainersModel::ContainersModelRoles::ConfigRole)); + + //todo error handling + qApp->processEvents(); + emit connectToVpn(serverIndex, credentials, container, containerConfig); + isConnected = true; + + +// int serverIndex = m_settings->defaultServerIndex(); +// ServerCredentials credentials = m_settings->serverCredentials(serverIndex); +// DockerContainer container = m_settings->defaultContainer(serverIndex); + +// if (m_settings->containers(serverIndex).isEmpty()) { +// set_labelErrorText(tr("VPN Protocols is not installed.\n Please install VPN container at first")); +// set_pushButtonConnectChecked(false); +// return; +// } + +// if (container == DockerContainer::None) { +// set_labelErrorText(tr("VPN Protocol not chosen")); +// set_pushButtonConnectChecked(false); +// return; +// } + + +// const QJsonObject &containerConfig = m_settings->containerConfig(serverIndex, container); + +// set_labelErrorText(""); +// set_pushButtonConnectChecked(true); +// set_pushButtonConnectEnabled(false); + +// qApp->processEvents(); + +// emit connectToVpn(serverIndex, credentials, container, containerConfig); +} + +bool ConnectionController::closeVpnConnection() +{ + emit disconnectFromVpn(); +} + diff --git a/client/ui/controllers/connectionController.h b/client/ui/controllers/connectionController.h new file mode 100644 index 00000000..125e9204 --- /dev/null +++ b/client/ui/controllers/connectionController.h @@ -0,0 +1,33 @@ +#ifndef CONNECTIONCONTROLLER_H +#define CONNECTIONCONTROLLER_H + +#include "ui/models/servers_model.h" +#include "ui/models/containers_model.h" + +class ConnectionController : public QObject +{ + Q_OBJECT + +public: + explicit ConnectionController(const QSharedPointer &serversModel, + const QSharedPointer &containersModel, + QObject *parent = nullptr); + +public slots: + bool onConnectionButtonClicked(); + +signals: + void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig); + void disconnectFromVpn(); + +private: + bool openVpnConnection(); + bool closeVpnConnection(); + + QSharedPointer m_serversModel; + QSharedPointer m_containersModel; + + bool isConnected = false; +}; + +#endif // CONNECTIONCONTROLLER_H diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index c7aa0ec1..db7572e6 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -17,7 +17,7 @@ bool ContainersModel::setData(const QModelIndex &index, const QVariant &value, i return false; } - if (role == DefaultRole) { + if (role == IsDefaultRole) { DockerContainer container = ContainerProps::allContainers().at(index.row()); m_settings->setDefaultContainer(m_selectedServerIndex, container); } @@ -33,22 +33,23 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const return QVariant(); } - DockerContainer c = ContainerProps::allContainers().at(index.row()); - if (role == NameRole) { - return ContainerProps::containerHumanNames().value(c); - } - if (role == DescRole) { - return ContainerProps::containerDescriptions().value(c); - } - if (role == DefaultRole) { - return c == m_settings->defaultContainer(m_selectedServerIndex); - } - if (role == ServiceTypeRole) { - return ContainerProps::containerService(c); - } - if (role == IsInstalledRole) { - return m_settings->containers(m_selectedServerIndex).contains(c); + DockerContainer container = ContainerProps::allContainers().at(index.row()); + + switch (role) { + case NameRole: + return ContainerProps::containerHumanNames().value(container); + case DescRole: + return ContainerProps::containerDescriptions().value(container); + case ConfigRole: + return m_settings->containerConfig(m_selectedServerIndex, container); + case ServiceTypeRole: + return ContainerProps::containerService(container); + case IsInstalledRole: + return m_settings->containers(m_selectedServerIndex).contains(container); + case IsDefaultRole: + return container == m_settings->defaultContainer(m_selectedServerIndex); } + return QVariant(); } @@ -71,12 +72,17 @@ QString ContainersModel::getCurrentlyInstalledContainerName() return data(m_currentlyInstalledContainerIndex, NameRole).toString(); } +DockerContainer ContainersModel::getDefaultContainer() +{ + return m_settings->defaultContainer(m_selectedServerIndex); +} + QHash ContainersModel::roleNames() const { QHash roles; - roles[NameRole] = "name_role"; - roles[DescRole] = "desc_role"; - roles[DefaultRole] = "default_role"; - roles[ServiceTypeRole] = "service_type_role"; - roles[IsInstalledRole] = "is_installed_role"; + roles[NameRole] = "name"; + roles[DescRole] = "description"; + roles[ServiceTypeRole] = "serviceType"; + roles[IsInstalledRole] = "isInstalled"; + roles[IsDefaultRole] = "isDefault"; return roles; } diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index ba0fea57..c56511db 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -15,12 +15,13 @@ class ContainersModel : public QAbstractListModel public: ContainersModel(std::shared_ptr settings, QObject *parent = nullptr); public: - enum SiteRoles { + enum ContainersModelRoles { NameRole = Qt::UserRole + 1, DescRole, - DefaultRole, ServiceTypeRole, - IsInstalledRole + ConfigRole, + IsInstalledRole, + IsDefaultRole }; int rowCount(const QModelIndex &parent = QModelIndex()) const override; @@ -32,6 +33,9 @@ public: Q_INVOKABLE QString getCurrentlyInstalledContainerName(); +public slots: + DockerContainer getDefaultContainer(); + protected: QHash roleNames() const override; diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 44ed2619..dccf59b9 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -37,36 +37,23 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const const QJsonArray &servers = m_settings->serversArray(); const QJsonObject server = servers.at(index.row()).toObject(); - if (role == DescRole) { + switch (role) { + case DescRole: { auto description = server.value(config_key::description).toString(); if (description.isEmpty()) { return server.value(config_key::hostName).toString(); } return description; } - if (role == AddressRole) { + case AddressRole: return server.value(config_key::hostName).toString(); - } - if (role == IsDefaultRole) { + case CredentialsRole: + return QVariant::fromValue(m_settings->serverCredentials(index.row())); + case IsDefaultRole: return index.row() == m_settings->defaultServerIndex(); } + return QVariant(); - - -// if (!index.isValid() || index.row() < 0 -// || index.row() >= static_cast(m_data.size())) { -// return QVariant(); -// } -// if (role == DescRole) { -// return m_data[index.row()].desc; -// } -// if (role == AddressRole) { -// return m_data[index.row()].address; -// } -// if (role == IsDefaultRole) { -// return m_data[index.row()].isDefault; -// } -// return QVariant(); } void ServersModel::setDefaultServerIndex(int index) diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index cd46846b..30c8b5a2 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -18,6 +18,7 @@ public: enum ServersModelRoles { DescRole = Qt::UserRole + 1, AddressRole, + CredentialsRole, IsDefaultRole }; diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml new file mode 100644 index 00000000..5457cf55 --- /dev/null +++ b/client/ui/qml/Components/ConnectButton.qml @@ -0,0 +1,40 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Button { + id: root + + implicitHeight: 190 + implicitWidth: 190 + + background: Rectangle { + id: background + + radius: parent.width * 0.5 + + color: "transparent" + + border.width: 2 + border.color: "white" + } + + contentItem: Text { + height: 24 + + font.family: "PT Root UI VF" + font.weight: 700 + font.pixelSize: 20 + + color: "#D7D8DB" + text: root.text + + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + + onClicked: { + background.color = "red" + ConnectionController.onConnectionButtonClicked() + } +} diff --git a/client/ui/qml/Controls2/TextTypes/ButtonTextType.qml b/client/ui/qml/Controls2/TextTypes/ButtonTextType.qml index 93a36578..d26594d6 100644 --- a/client/ui/qml/Controls2/TextTypes/ButtonTextType.qml +++ b/client/ui/qml/Controls2/TextTypes/ButtonTextType.qml @@ -5,7 +5,7 @@ Text { color: "#D7D8DB" font.pixelSize: 16 - font.weight: 500 + font.weight: Font.Medium font.family: "PT Root UI VF" wrapMode: Text.WordWrap diff --git a/client/ui/qml/Controls2/TextTypes/Header1TextType.qml b/client/ui/qml/Controls2/TextTypes/Header1TextType.qml index dbc04b6a..99addc7b 100644 --- a/client/ui/qml/Controls2/TextTypes/Header1TextType.qml +++ b/client/ui/qml/Controls2/TextTypes/Header1TextType.qml @@ -5,7 +5,7 @@ Text { color: "#D7D8DB" font.pixelSize: 36 - font.weight: 700 + font.weight: Font.Bold font.family: "PT Root UI VF" font.letterSpacing: -0.03 diff --git a/client/ui/qml/Controls2/TextTypes/Header2TextType.qml b/client/ui/qml/Controls2/TextTypes/Header2TextType.qml index 1400ceb2..ed96f6f1 100644 --- a/client/ui/qml/Controls2/TextTypes/Header2TextType.qml +++ b/client/ui/qml/Controls2/TextTypes/Header2TextType.qml @@ -5,7 +5,7 @@ Text { color: "#D7D8DB" font.pixelSize: 25 - font.weight: 700 + font.weight: Font.Bold font.family: "PT Root UI VF" wrapMode: Text.WordWrap diff --git a/client/ui/qml/Controls2/TextTypes/LabelTextType.qml b/client/ui/qml/Controls2/TextTypes/LabelTextType.qml index 38649022..a2ffa18b 100644 --- a/client/ui/qml/Controls2/TextTypes/LabelTextType.qml +++ b/client/ui/qml/Controls2/TextTypes/LabelTextType.qml @@ -5,7 +5,7 @@ Text { color: "#878B91" font.pixelSize: 13 - font.weight: 400 + font.weight: Font.Normal font.family: "PT Root UI VF" font.letterSpacing: 0.02 diff --git a/client/ui/qml/Controls2/TextTypes/ParagraphTextType.qml b/client/ui/qml/Controls2/TextTypes/ParagraphTextType.qml index 269830bc..74b155ab 100644 --- a/client/ui/qml/Controls2/TextTypes/ParagraphTextType.qml +++ b/client/ui/qml/Controls2/TextTypes/ParagraphTextType.qml @@ -5,7 +5,7 @@ Text { color: "#D7D8DB" font.pixelSize: 16 - font.weight: 400 + font.weight: Font.Normal font.family: "PT Root UI VF" wrapMode: Text.WordWrap diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index f4f4d13c..40eba148 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -12,6 +12,7 @@ import "../Pages" import "../Controls2" import "../Controls2/TextTypes" import "../Config" +import "../Components" PageBase { id: root @@ -24,6 +25,12 @@ PageBase { property string currentServerName: serversMenuContent.currentItem.delegateData.desc property string currentServerDescription: serversMenuContent.currentItem.delegateData.address + ConnectButton { + anchors.centerIn: parent + + text: "Подключиться" + } + Rectangle { id: buttonBackground anchors.fill: buttonContent @@ -136,11 +143,11 @@ PageBase { sourceModel: ContainersModel filters: [ ValueFilter { - roleName: "service_type_role" + roleName: "serviceType" value: ProtocolEnum.Vpn }, ValueFilter { - roleName: "is_installed_role" + roleName: "isInstalled" value: true } ] @@ -153,7 +160,7 @@ PageBase { borderWidth: 0 buttonImageColor: "#0E0E11" - buttonMaximumWidth: 150 + buttonMaximumWidth: 150 //todo make it dynamic defaultColor: "#D7D8DB" @@ -183,7 +190,7 @@ PageBase { checked: { if (modelData !== null) { - return modelData.default_role + return modelData.isDefault } return false } @@ -208,7 +215,7 @@ PageBase { // todo remove dirty hack? text: { if (modelData !== null) { - return modelData.name_role + return modelData.name } else return "" } @@ -235,7 +242,7 @@ PageBase { } onClicked: { - modelData.default_role = true + modelData.isDefault = true containersDropDown.text = containerRadioButtonText.text containersDropDown.menuVisible = false @@ -243,14 +250,16 @@ PageBase { } Component.onCompleted: { - if (modelData !== null && modelData.default_role) { - containersDropDown.text = modelData.name_role + if (modelData !== null && modelData.isDefault) { + containersDropDown.text = modelData.name } } } } BasicButtonType { + id: dnsButton + implicitHeight: 40 text: "Amnezia DNS" From 116fa6777b400261f71aa9126e956febe09d37f2 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sun, 14 May 2023 21:11:19 +0800 Subject: [PATCH 017/278] added logic to the connect to vpn button --- client/3rd/qtkeychain | 2 +- client/amnezia_application.cpp | 8 +- client/images/connectionOff.svg | 18 ++++ client/images/connectionOn.svg | 17 ++++ client/images/connectionProgress.svg | 30 +++++++ client/protocols/openvpnovercloakprotocol.cpp | 4 +- client/protocols/openvpnprotocol.cpp | 20 ++--- client/protocols/shadowsocksvpnprotocol.cpp | 4 +- client/protocols/vpnprotocol.cpp | 34 +++---- client/protocols/vpnprotocol.h | 40 +++++++-- client/protocols/wireguardprotocol.cpp | 14 +-- client/resources.qrc | 3 + .../ui/controllers/connectionController.cpp | 10 ++- client/ui/controllers/connectionController.h | 6 +- client/ui/notificationhandler.cpp | 8 +- client/ui/notificationhandler.h | 2 +- client/ui/pages_logic/SitesLogic.cpp | 4 +- client/ui/pages_logic/VpnLogic.cpp | 22 ++--- client/ui/pages_logic/VpnLogic.h | 2 +- client/ui/qml/Components/ConnectButton.qml | 88 +++++++++++++++++-- client/ui/qml/Pages2/PageHome.qml | 2 - client/ui/qml/Pages2/PageStart.qml | 10 +-- client/ui/systemtray_notificationhandler.cpp | 22 ++--- client/ui/systemtray_notificationhandler.h | 4 +- client/ui/uilogic.cpp | 6 +- client/vpnconnection.cpp | 32 +++---- client/vpnconnection.h | 6 +- 27 files changed, 293 insertions(+), 125 deletions(-) create mode 100644 client/images/connectionOff.svg create mode 100644 client/images/connectionOn.svg create mode 100644 client/images/connectionProgress.svg diff --git a/client/3rd/qtkeychain b/client/3rd/qtkeychain index c6f0b663..8bbaa6d8 160000 --- a/client/3rd/qtkeychain +++ b/client/3rd/qtkeychain @@ -1 +1 @@ -Subproject commit c6f0b66318f8da6917fb4681103f7303b1836194 +Subproject commit 8bbaa6d8302cf0747d9786ace4dd13c7fb746502 diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 6fadcabb..a8231fce 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -109,6 +109,9 @@ void AmneziaApplication::init() m_vpnConnection.reset(new VpnConnection(m_settings, m_configurator)); m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel)); + + connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, + m_connectionController.get(), &ConnectionController::connectionStateChanged); connect(m_connectionController.get(), &ConnectionController::connectToVpn, m_vpnConnection.get(), &VpnConnection::connectToVpn, Qt::QueuedConnection); connect(m_connectionController.get(), &ConnectionController::disconnectFromVpn, @@ -156,7 +159,6 @@ void AmneziaApplication::init() void AmneziaApplication::registerTypes() { - qRegisterMetaType("VpnProtocol::VpnConnectionState"); qRegisterMetaType("ServerCredentials"); qRegisterMetaType("DockerContainer"); @@ -164,7 +166,6 @@ void AmneziaApplication::registerTypes() qRegisterMetaType("Proto"); qRegisterMetaType("ServiceType"); qRegisterMetaType("Page"); - qRegisterMetaType("ConnectionState"); qRegisterMetaType("PageProtocolLogicBase *"); @@ -181,6 +182,9 @@ void AmneziaApplication::registerTypes() m_protocolProps = new ProtocolProps; qmlRegisterSingletonInstance("ProtocolProps", 1, 0, "ProtocolProps", m_protocolProps); + + // + Vpn::declareQmlVpnConnectionStateEnum(); } void AmneziaApplication::loadFonts() diff --git a/client/images/connectionOff.svg b/client/images/connectionOff.svg new file mode 100644 index 00000000..27905ff9 --- /dev/null +++ b/client/images/connectionOff.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/client/images/connectionOn.svg b/client/images/connectionOn.svg new file mode 100644 index 00000000..ef317622 --- /dev/null +++ b/client/images/connectionOn.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/client/images/connectionProgress.svg b/client/images/connectionProgress.svg new file mode 100644 index 00000000..8c4024c9 --- /dev/null +++ b/client/images/connectionProgress.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/protocols/openvpnovercloakprotocol.cpp b/client/protocols/openvpnovercloakprotocol.cpp index 55939895..52bcae4b 100644 --- a/client/protocols/openvpnovercloakprotocol.cpp +++ b/client/protocols/openvpnovercloakprotocol.cpp @@ -66,7 +66,7 @@ ErrorCode OpenVpnOverCloakProtocol::start() m_errorHandlerConnection = connect(&m_ckProcess, QOverload::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus){ qDebug().noquote() << "OpenVpnOverCloakProtocol finished, exitCode, exiStatus" << exitCode << exitStatus; - setConnectionState(VpnProtocol::Disconnected); + setConnectionState(Vpn::ConnectionState::Disconnected); if (exitStatus != QProcess::NormalExit){ emit protocolError(amnezia::ErrorCode::CloakExecutableCrashed); stop(); @@ -81,7 +81,7 @@ ErrorCode OpenVpnOverCloakProtocol::start() m_ckProcess.waitForStarted(); if (m_ckProcess.state() == QProcess::ProcessState::Running) { - setConnectionState(VpnConnectionState::Connecting); + setConnectionState(Vpn::ConnectionState::Connecting); return OpenVpnProtocol::start(); } diff --git a/client/protocols/openvpnprotocol.cpp b/client/protocols/openvpnprotocol.cpp index 150e84be..273d7b76 100644 --- a/client/protocols/openvpnprotocol.cpp +++ b/client/protocols/openvpnprotocol.cpp @@ -45,16 +45,16 @@ void OpenVpnProtocol::stop() // TODO: need refactoring // sendTermSignal() will even return true while server connected ??? - if ((m_connectionState == VpnProtocol::Preparing) || - (m_connectionState == VpnProtocol::Connecting) || - (m_connectionState == VpnProtocol::Connected) || - (m_connectionState == VpnProtocol::Reconnecting)) { + if ((m_connectionState == Vpn::ConnectionState::Preparing) || + (m_connectionState == Vpn::ConnectionState::Connecting) || + (m_connectionState == Vpn::ConnectionState::Connected) || + (m_connectionState == Vpn::ConnectionState::Reconnecting)) { if (!sendTermSignal()) { killOpenVpnProcess(); } m_managementServer.stop(); qApp->processEvents(); - setConnectionState(VpnProtocol::Disconnecting); + setConnectionState(Vpn::ConnectionState::Disconnecting); } } @@ -175,7 +175,7 @@ ErrorCode OpenVpnProtocol::start() return lastError(); } - setConnectionState(VpnConnectionState::Connecting); + setConnectionState(Vpn::ConnectionState::Connecting); m_openVpnProcess = IpcClient::CreatePrivilegedProcess(); @@ -208,7 +208,7 @@ ErrorCode OpenVpnProtocol::start() }); connect(m_openVpnProcess.data(), &PrivilegedProcess::finished, this, [&]() { - setConnectionState(VpnConnectionState::Disconnected); + setConnectionState(Vpn::ConnectionState::Disconnected); }); m_openVpnProcess->start(); @@ -256,14 +256,14 @@ void OpenVpnProtocol::onReadyReadDataFromManagementServer() if (line.contains("CONNECTED,SUCCESS")) { sendByteCount(); stopTimeoutTimer(); - setConnectionState(VpnProtocol::Connected); + setConnectionState(Vpn::ConnectionState::Connected); continue; } else if (line.contains("EXITING,SIGTER")) { //openVpnStateSigTermHandler(); - setConnectionState(VpnProtocol::Disconnecting); + setConnectionState(Vpn::ConnectionState::Disconnecting); continue; } else if (line.contains("RECONNECTING")) { - setConnectionState(VpnProtocol::Reconnecting); + setConnectionState(Vpn::ConnectionState::Reconnecting); continue; } } diff --git a/client/protocols/shadowsocksvpnprotocol.cpp b/client/protocols/shadowsocksvpnprotocol.cpp index 82ae08b8..0ffc2768 100644 --- a/client/protocols/shadowsocksvpnprotocol.cpp +++ b/client/protocols/shadowsocksvpnprotocol.cpp @@ -66,7 +66,7 @@ ErrorCode ShadowSocksVpnProtocol::start() connect(&m_ssProcess, QOverload::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus){ qDebug().noquote() << "ShadowSocksVpnProtocol finished, exitCode, exiStatus" << exitCode << exitStatus; - setConnectionState(VpnProtocol::Disconnected); + setConnectionState(Vpn::ConnectionState::Disconnected); if (exitStatus != QProcess::NormalExit){ emit protocolError(amnezia::ErrorCode::ShadowSocksExecutableCrashed); stop(); @@ -81,7 +81,7 @@ ErrorCode ShadowSocksVpnProtocol::start() m_ssProcess.waitForStarted(); if (m_ssProcess.state() == QProcess::ProcessState::Running) { - setConnectionState(VpnConnectionState::Connecting); + setConnectionState(Vpn::ConnectionState::Connecting); return OpenVpnProtocol::start(); } diff --git a/client/protocols/vpnprotocol.cpp b/client/protocols/vpnprotocol.cpp index a8f392e9..841d307c 100644 --- a/client/protocols/vpnprotocol.cpp +++ b/client/protocols/vpnprotocol.cpp @@ -18,7 +18,7 @@ VpnProtocol::VpnProtocol(const QJsonObject &configuration, QObject* parent) : QObject(parent), - m_connectionState(VpnConnectionState::Unknown), + m_connectionState(Vpn::ConnectionState::Unknown), m_rawConfig(configuration), m_timeoutTimer(new QTimer(this)), m_receivedBytes(0), @@ -32,7 +32,7 @@ void VpnProtocol::setLastError(ErrorCode lastError) { m_lastError = lastError; if (lastError){ - setConnectionState(VpnConnectionState::Error); + setConnectionState(Vpn::ConnectionState::Error); } qCritical().noquote() << "VpnProtocol error, code" << m_lastError << errorString(m_lastError); } @@ -60,7 +60,7 @@ void VpnProtocol::stopTimeoutTimer() m_timeoutTimer->stop(); } -VpnProtocol::VpnConnectionState VpnProtocol::connectionState() const +Vpn::ConnectionState VpnProtocol::connectionState() const { return m_connectionState; } @@ -76,19 +76,19 @@ void VpnProtocol::setBytesChanged(quint64 receivedBytes, quint64 sentBytes) m_sentBytes = sentBytes; } -void VpnProtocol::setConnectionState(VpnProtocol::VpnConnectionState state) +void VpnProtocol::setConnectionState(Vpn::ConnectionState state) { qDebug() << "VpnProtocol::setConnectionState" << textConnectionState(state); if (m_connectionState == state) { return; } - if (m_connectionState == VpnConnectionState::Disconnected && state == VpnConnectionState::Disconnecting) { + if (m_connectionState == Vpn::ConnectionState::Disconnected && state == Vpn::ConnectionState::Disconnecting) { return; } m_connectionState = state; - if (m_connectionState == VpnConnectionState::Disconnected) { + if (m_connectionState == Vpn::ConnectionState::Disconnected) { m_receivedBytes = 0; m_sentBytes = 0; } @@ -124,17 +124,17 @@ QString VpnProtocol::routeGateway() const return m_routeGateway; } -QString VpnProtocol::textConnectionState(VpnConnectionState connectionState) +QString VpnProtocol::textConnectionState(Vpn::ConnectionState connectionState) { switch (connectionState) { - case VpnConnectionState::Unknown: return tr("Unknown"); - case VpnConnectionState::Disconnected: return tr("Disconnected"); - case VpnConnectionState::Preparing: return tr("Preparing"); - case VpnConnectionState::Connecting: return tr("Connecting..."); - case VpnConnectionState::Connected: return tr("Connected"); - case VpnConnectionState::Disconnecting: return tr("Disconnecting..."); - case VpnConnectionState::Reconnecting: return tr("Reconnecting..."); - case VpnConnectionState::Error: return tr("Error"); + case Vpn::ConnectionState::Unknown: return tr("Unknown"); + case Vpn::ConnectionState::Disconnected: return tr("Disconnected"); + case Vpn::ConnectionState::Preparing: return tr("Preparing"); + case Vpn::ConnectionState::Connecting: return tr("Connecting..."); + case Vpn::ConnectionState::Connected: return tr("Connected"); + case Vpn::ConnectionState::Disconnecting: return tr("Disconnecting..."); + case Vpn::ConnectionState::Reconnecting: return tr("Reconnecting..."); + case Vpn::ConnectionState::Error: return tr("Error"); default: ; } @@ -149,10 +149,10 @@ QString VpnProtocol::textConnectionState() const bool VpnProtocol::isConnected() const { - return m_connectionState == VpnConnectionState::Connected; + return m_connectionState == Vpn::ConnectionState::Connected; } bool VpnProtocol::isDisconnected() const { - return m_connectionState == VpnConnectionState::Disconnected; + return m_connectionState == Vpn::ConnectionState::Disconnected; } diff --git a/client/protocols/vpnprotocol.h b/client/protocols/vpnprotocol.h index 4b6876d5..bb71a5de 100644 --- a/client/protocols/vpnprotocol.h +++ b/client/protocols/vpnprotocol.h @@ -12,6 +12,33 @@ using namespace amnezia; class QTimer; +//todo change name +namespace Vpn +{ + Q_NAMESPACE + enum ConnectionState { + Unknown, + Disconnected, + Preparing, + Connecting, + Connected, + Disconnecting, + Reconnecting, + Error + }; + Q_ENUM_NS(ConnectionState) + + static void declareQmlVpnConnectionStateEnum() { + qmlRegisterUncreatableMetaObject( + Vpn::staticMetaObject, + "ConnectionState", + 1, 0, + "ConnectionState", + "Error: only enums" + ); + } +} + class VpnProtocol : public QObject { Q_OBJECT @@ -20,10 +47,7 @@ public: explicit VpnProtocol(const QJsonObject& configuration, QObject* parent = nullptr); virtual ~VpnProtocol() override = default; - enum VpnConnectionState {Unknown, Disconnected, Preparing, Connecting, Connected, Disconnecting, Reconnecting, Error}; - Q_ENUM(VpnConnectionState) - - static QString textConnectionState(VpnConnectionState connectionState); + static QString textConnectionState(Vpn::ConnectionState connectionState); virtual ErrorCode prepare() { return ErrorCode::NoError; } @@ -32,7 +56,7 @@ public: virtual ErrorCode start() = 0; virtual void stop() = 0; - VpnConnectionState connectionState() const; + Vpn::ConnectionState connectionState() const; ErrorCode lastError() const; QString textConnectionState() const; void setLastError(ErrorCode lastError); @@ -44,7 +68,7 @@ public: signals: void bytesChanged(quint64 receivedBytes, quint64 sentBytes); - void connectionStateChanged(VpnProtocol::VpnConnectionState state); + void connectionStateChanged(Vpn::ConnectionState state); void timeoutTimerEvent(); void protocolError(amnezia::ErrorCode e); @@ -52,13 +76,13 @@ public slots: virtual void onTimeout(); // todo: remove? void setBytesChanged(quint64 receivedBytes, quint64 sentBytes); - void setConnectionState(VpnProtocol::VpnConnectionState state); + void setConnectionState(Vpn::ConnectionState state); protected: void startTimeoutTimer(); void stopTimeoutTimer(); - VpnConnectionState m_connectionState; + Vpn::ConnectionState m_connectionState; QString m_routeGateway; QString m_vpnLocalAddress; diff --git a/client/protocols/wireguardprotocol.cpp b/client/protocols/wireguardprotocol.cpp index 666bf80d..4f66f5a1 100644 --- a/client/protocols/wireguardprotocol.cpp +++ b/client/protocols/wireguardprotocol.cpp @@ -51,7 +51,7 @@ void WireguardProtocol::stop() connect(m_wireguardStopProcess.data(), &PrivilegedProcess::errorOccurred, this, [this](QProcess::ProcessError error) { qDebug() << "WireguardProtocol::WireguardProtocol Stop errorOccurred" << error; - setConnectionState(VpnConnectionState::Disconnected); + setConnectionState(Vpn::ConnectionState::Disconnected); }); connect(m_wireguardStopProcess.data(), &PrivilegedProcess::stateChanged, this, [this](QProcess::ProcessState newState) { @@ -62,12 +62,12 @@ void WireguardProtocol::stop() if (IpcClient::Interface()) { QRemoteObjectPendingReply result = IpcClient::Interface()->isWireguardRunning(); if (result.returnValue()) { - setConnectionState(VpnProtocol::Disconnected); + setConnectionState(Vpn::ConnectionState::Disconnected); return; } } else { qCritical() << "IPC client not initialized"; - setConnectionState(VpnProtocol::Disconnected); + setConnectionState(Vpn::ConnectionState::Disconnected); return; } #endif @@ -75,7 +75,7 @@ void WireguardProtocol::stop() m_wireguardStopProcess->start(); m_wireguardStopProcess->waitForFinished(10000); - setConnectionState(VpnProtocol::Disconnected); + setConnectionState(Vpn::ConnectionState::Disconnected); #endif } @@ -156,7 +156,7 @@ ErrorCode WireguardProtocol::start() return lastError(); } - setConnectionState(VpnConnectionState::Connecting); + setConnectionState(Vpn::ConnectionState::Connecting); m_wireguardStartProcess = IpcClient::CreatePrivilegedProcess(); @@ -179,7 +179,7 @@ ErrorCode WireguardProtocol::start() connect(m_wireguardStartProcess.data(), &PrivilegedProcess::errorOccurred, this, [this](QProcess::ProcessError error) { qDebug() << "WireguardProtocol::WireguardProtocol errorOccurred" << error; - setConnectionState(VpnConnectionState::Disconnected); + setConnectionState(Vpn::ConnectionState::Disconnected); }); connect(m_wireguardStartProcess.data(), &PrivilegedProcess::stateChanged, this, [this](QProcess::ProcessState newState) { @@ -187,7 +187,7 @@ ErrorCode WireguardProtocol::start() }); connect(m_wireguardStartProcess.data(), &PrivilegedProcess::finished, this, [this]() { - setConnectionState(VpnConnectionState::Connected); + setConnectionState(Vpn::ConnectionState::Connected); }); connect(m_wireguardStartProcess.data(), &PrivilegedProcess::readyRead, this, [this]() { diff --git a/client/resources.qrc b/client/resources.qrc index 1ea7bf38..f75c8132 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -223,5 +223,8 @@ ui/qml/Controls2/Header2Type.qml images/controls/plus.svg ui/qml/Components/ConnectButton.qml + images/connectionProgress.svg + images/connectionOff.svg + images/connectionOn.svg diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index 41dbed64..ce52a2c8 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -11,13 +11,18 @@ ConnectionController::ConnectionController(const QSharedPointer &s bool ConnectionController::onConnectionButtonClicked() { - if (!isConnected) { + if (!isConnected()) { openVpnConnection(); } else { closeVpnConnection(); } } +bool ConnectionController::isConnected() +{ + return m_isConnected; +} + bool ConnectionController::openVpnConnection() { int serverIndex = m_serversModel->getDefaultServerIndex(); @@ -33,7 +38,7 @@ bool ConnectionController::openVpnConnection() //todo error handling qApp->processEvents(); emit connectToVpn(serverIndex, credentials, container, containerConfig); - isConnected = true; + m_isConnected = true; // int serverIndex = m_settings->defaultServerIndex(); @@ -67,5 +72,6 @@ bool ConnectionController::openVpnConnection() bool ConnectionController::closeVpnConnection() { emit disconnectFromVpn(); + m_isConnected = false; } diff --git a/client/ui/controllers/connectionController.h b/client/ui/controllers/connectionController.h index 125e9204..8282a582 100644 --- a/client/ui/controllers/connectionController.h +++ b/client/ui/controllers/connectionController.h @@ -3,6 +3,7 @@ #include "ui/models/servers_model.h" #include "ui/models/containers_model.h" +#include "protocols/vpnprotocol.h" class ConnectionController : public QObject { @@ -16,9 +17,12 @@ public: public slots: bool onConnectionButtonClicked(); + bool isConnected(); + signals: void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig); void disconnectFromVpn(); + void connectionStateChanged(Vpn::ConnectionState state); private: bool openVpnConnection(); @@ -27,7 +31,7 @@ private: QSharedPointer m_serversModel; QSharedPointer m_containersModel; - bool isConnected = false; + bool m_isConnected = false; }; #endif // CONNECTIONCONTROLLER_H diff --git a/client/ui/notificationhandler.cpp b/client/ui/notificationhandler.cpp index 779569d7..f932eb17 100644 --- a/client/ui/notificationhandler.cpp +++ b/client/ui/notificationhandler.cpp @@ -52,9 +52,9 @@ NotificationHandler::~NotificationHandler() { s_instance = nullptr; } -void NotificationHandler::setConnectionState(VpnProtocol::VpnConnectionState state) +void NotificationHandler::setConnectionState(Vpn::ConnectionState state) { - if (state != VpnProtocol::Connected && state != VpnProtocol::Disconnected) { + if (state != Vpn::ConnectionState::Connected && state != Vpn::ConnectionState::Disconnected) { return; } @@ -62,14 +62,14 @@ void NotificationHandler::setConnectionState(VpnProtocol::VpnConnectionState sta QString message; switch (state) { - case VpnProtocol::VpnConnectionState::Connected: + case Vpn::ConnectionState::Connected: m_connected = true; title = tr("AmneziaVPN"); message = tr("VPN Connected"); break; - case VpnProtocol::VpnConnectionState::Disconnected: + case Vpn::ConnectionState::Disconnected: if (m_connected) { m_connected = false; title = tr("AmneziaVPN"); diff --git a/client/ui/notificationhandler.h b/client/ui/notificationhandler.h index b87e9575..9a2182de 100644 --- a/client/ui/notificationhandler.h +++ b/client/ui/notificationhandler.h @@ -31,7 +31,7 @@ public: void messageClickHandle(); public slots: - virtual void setConnectionState(VpnProtocol::VpnConnectionState state); + virtual void setConnectionState(Vpn::ConnectionState state); signals: void notificationShown(const QString& title, const QString& message); diff --git a/client/ui/pages_logic/SitesLogic.cpp b/client/ui/pages_logic/SitesLogic.cpp index 17357073..bf926e56 100644 --- a/client/ui/pages_logic/SitesLogic.cpp +++ b/client/ui/pages_logic/SitesLogic.cpp @@ -116,14 +116,14 @@ void SitesLogic::onPushButtonSitesDeleteClicked(QStringList items) if (!ok || row < 0 || row >= siteModel->rowCount()) return; sites.append(siteModel->data(row, 0).toString()); - if (uiLogic()->m_vpnConnection->connectionState() == VpnProtocol::Connected) { + if (uiLogic()->m_vpnConnection->connectionState() == Vpn::ConnectionState::Connected) { ips.append(siteModel->data(row, 1).toString()); } } m_settings->removeVpnSites(mode, sites); - if (uiLogic()->m_vpnConnection->connectionState() == VpnProtocol::Connected) { + if (uiLogic()->m_vpnConnection->connectionState() == Vpn::ConnectionState::Connected) { uiLogic()->m_vpnConnection->deleteRoutes(ips); uiLogic()->m_vpnConnection->flushDns(); } diff --git a/client/ui/pages_logic/VpnLogic.cpp b/client/ui/pages_logic/VpnLogic.cpp index d3df1a08..4db54244 100644 --- a/client/ui/pages_logic/VpnLogic.cpp +++ b/client/ui/pages_logic/VpnLogic.cpp @@ -42,7 +42,7 @@ VpnLogic::VpnLogic(UiLogic *logic, QObject *parent): }); } else { - onConnectionStateChanged(VpnProtocol::Disconnected); + onConnectionStateChanged(Vpn::ConnectionState::Disconnected); } } @@ -119,7 +119,7 @@ void VpnLogic::onBytesChanged(quint64 receivedData, quint64 sentData) set_labelSpeedSentText(VpnConnection::bytesPerSecToText(sentData)); } -void VpnLogic::onConnectionStateChanged(VpnProtocol::VpnConnectionState state) +void VpnLogic::onConnectionStateChanged(Vpn::ConnectionState state) { qDebug() << "VpnLogic::onConnectionStateChanged" << VpnProtocol::textConnectionState(state); if (uiLogic()->m_vpnConnection == NULL) { @@ -134,50 +134,50 @@ void VpnLogic::onConnectionStateChanged(VpnProtocol::VpnConnectionState state) set_labelStateText(VpnProtocol::textConnectionState(state)); switch (state) { - case VpnProtocol::Disconnected: + case Vpn::ConnectionState::Disconnected: onBytesChanged(0,0); pbConnectChecked = false; pbConnectEnabled = true; pbConnectVisible = true; rbModeEnabled = true; break; - case VpnProtocol::Preparing: + case Vpn::ConnectionState::Preparing: pbConnectChecked = true; pbConnectEnabled = false; pbConnectVisible = false; rbModeEnabled = false; break; - case VpnProtocol::Connecting: + case Vpn::ConnectionState::Connecting: pbConnectChecked = true; pbConnectEnabled = false; pbConnectVisible = false; rbModeEnabled = false; break; - case VpnProtocol::Connected: + case Vpn::ConnectionState::Connected: pbConnectChecked = true; pbConnectEnabled = true; pbConnectVisible = true; rbModeEnabled = false; break; - case VpnProtocol::Disconnecting: + case Vpn::ConnectionState::Disconnecting: pbConnectChecked = false; pbConnectEnabled = false; pbConnectVisible = false; rbModeEnabled = false; break; - case VpnProtocol::Reconnecting: + case Vpn::ConnectionState::Reconnecting: pbConnectChecked = true; pbConnectEnabled = true; pbConnectVisible = false; rbModeEnabled = false; break; - case VpnProtocol::Error: + case Vpn::ConnectionState::Error: pbConnectChecked = false; pbConnectEnabled = true; pbConnectVisible = true; rbModeEnabled = true; break; - case VpnProtocol::Unknown: + case Vpn::ConnectionState::Unknown: pbConnectChecked = false; pbConnectEnabled = true; pbConnectVisible = true; @@ -241,6 +241,6 @@ void VpnLogic::onConnectWorker(int serverIndex, const ServerCredentials &credent void VpnLogic::onDisconnect() { - onConnectionStateChanged(VpnProtocol::Disconnected); + onConnectionStateChanged(Vpn::ConnectionState::Disconnected); emit disconnectFromVpn(); } diff --git a/client/ui/pages_logic/VpnLogic.h b/client/ui/pages_logic/VpnLogic.h index f7b21be2..a0f7763b 100644 --- a/client/ui/pages_logic/VpnLogic.h +++ b/client/ui/pages_logic/VpnLogic.h @@ -58,7 +58,7 @@ public slots: void onDisconnect(); void onBytesChanged(quint64 receivedBytes, quint64 sentBytes); - void onConnectionStateChanged(VpnProtocol::VpnConnectionState state); + void onConnectionStateChanged(Vpn::ConnectionState state); void onVpnProtocolError(amnezia::ErrorCode errorCode); signals: diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml index 5457cf55..bce652d8 100644 --- a/client/ui/qml/Components/ConnectButton.qml +++ b/client/ui/qml/Components/ConnectButton.qml @@ -2,21 +2,40 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import ConnectionState 1.0 + Button { id: root - implicitHeight: 190 - implicitWidth: 190 + property var isConnected: ConnectionController.isConnected - background: Rectangle { - id: background + text: "Подключиться" - radius: parent.width * 0.5 +// implicitHeight: 190 +// implicitWidth: 190 - color: "transparent" +// color: "transparent" - border.width: 2 - border.color: "white" + background: Image { + id: border + + source: connectionProccess.running ? "/images/connectionProgress.svg" : + ConnectionController.isConnected() ? "/images/connectionOff.svg" : "/images/connectionOn.svg" + + RotationAnimator { + id: connectionProccess + + target: border + running: false + from: 0 + to: 360 + loops: Animation.Infinite + duration: 1250 + } + + Behavior on source { + PropertyAnimation { duration: 200 } + } } contentItem: Text { @@ -34,7 +53,58 @@ Button { } onClicked: { - background.color = "red" ConnectionController.onConnectionButtonClicked() + console.log(connectionProccess.from) + } + + Connections { + target: ConnectionController + function onConnectionStateChanged(state) { + switch(state) { + case ConnectionState.Unknown: { + console.log("Unknown") + break + } + case ConnectionState.Disconnected: { + console.log("Disconnected") + connectionProccess.running = false + root.text = "Подключиться" + break + } + case ConnectionState.Preparing: { + console.log("Preparing") + break + } + case ConnectionState.Connecting: { + console.log("Connecting") + connectionProccess.running = true + root.text = "Подключение..." + break + } + case ConnectionState.Connected: { + console.log("Connected") + connectionProccess.running = false + root.text = "Подключено" + break + } + case ConnectionState.Disconnecting: { + console.log("Disconnecting") + connectionProccess.running = true + root.text = "Отключение..." + break + } + case ConnectionState.Reconnecting: { + console.log("Reconnecting") + connectionProccess.running = true + root.text = "Переподключение..." + break + } + case ConnectionState.Error: { + console.log("Error") + connectionProccess.running = false + break + } + } + } } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 40eba148..5dd0aaae 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -27,8 +27,6 @@ PageBase { ConnectButton { anchors.centerIn: parent - - text: "Подключиться" } Rectangle { diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 9e94ed28..220c6f19 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -23,14 +23,8 @@ PageBase { anchors.left: parent.left anchors.bottom: tabBar.top - width: { - console.log(parent.width) - return parent.width - } - height: { - console.log(root.height - tabBar.implicitHeight) - return root.height - tabBar.implicitHeight - } + width: parent.width + height: root.height - tabBar.implicitHeight PageHome { } diff --git a/client/ui/systemtray_notificationhandler.cpp b/client/ui/systemtray_notificationhandler.cpp index e142caf5..25ff9f6c 100644 --- a/client/ui/systemtray_notificationhandler.cpp +++ b/client/ui/systemtray_notificationhandler.cpp @@ -45,13 +45,13 @@ SystemTrayNotificationHandler::SystemTrayNotificationHandler(QObject* parent) : }); m_systemTrayIcon.setContextMenu(&m_menu); - setTrayState(VpnProtocol::Disconnected); + setTrayState(Vpn::ConnectionState::Disconnected); } SystemTrayNotificationHandler::~SystemTrayNotificationHandler() { } -void SystemTrayNotificationHandler::setConnectionState(VpnProtocol::VpnConnectionState state) +void SystemTrayNotificationHandler::setConnectionState(Vpn::ConnectionState state) { setTrayState(state); NotificationHandler::setConnectionState(state); @@ -73,47 +73,47 @@ void SystemTrayNotificationHandler::onTrayActivated(QSystemTrayIcon::ActivationR #endif } -void SystemTrayNotificationHandler::setTrayState(VpnProtocol::VpnConnectionState state) +void SystemTrayNotificationHandler::setTrayState(Vpn::ConnectionState state) { QString resourcesPath = ":/images/tray/%1"; switch (state) { - case VpnProtocol::Disconnected: + case Vpn::ConnectionState::Disconnected: setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName)); m_trayActionConnect->setEnabled(true); m_trayActionDisconnect->setEnabled(false); break; - case VpnProtocol::Preparing: + case Vpn::ConnectionState::Preparing: setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName)); m_trayActionConnect->setEnabled(false); m_trayActionDisconnect->setEnabled(true); break; - case VpnProtocol::Connecting: + case Vpn::ConnectionState::Connecting: setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName)); m_trayActionConnect->setEnabled(false); m_trayActionDisconnect->setEnabled(true); break; - case VpnProtocol::Connected: + case Vpn::ConnectionState::Connected: setTrayIcon(QString(resourcesPath).arg(ConnectedTrayIconName)); m_trayActionConnect->setEnabled(false); m_trayActionDisconnect->setEnabled(true); break; - case VpnProtocol::Disconnecting: + case Vpn::ConnectionState::Disconnecting: setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName)); m_trayActionConnect->setEnabled(false); m_trayActionDisconnect->setEnabled(true); break; - case VpnProtocol::Reconnecting: + case Vpn::ConnectionState::Reconnecting: setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName)); m_trayActionConnect->setEnabled(false); m_trayActionDisconnect->setEnabled(true); break; - case VpnProtocol::Error: + case Vpn::ConnectionState::Error: setTrayIcon(QString(resourcesPath).arg(ErrorTrayIconName)); m_trayActionConnect->setEnabled(true); m_trayActionDisconnect->setEnabled(false); break; - case VpnProtocol::Unknown: + case Vpn::ConnectionState::Unknown: default: m_trayActionConnect->setEnabled(false); m_trayActionDisconnect->setEnabled(true); diff --git a/client/ui/systemtray_notificationhandler.h b/client/ui/systemtray_notificationhandler.h index 46563bde..96134f14 100644 --- a/client/ui/systemtray_notificationhandler.h +++ b/client/ui/systemtray_notificationhandler.h @@ -17,7 +17,7 @@ public: explicit SystemTrayNotificationHandler(QObject* parent); ~SystemTrayNotificationHandler(); - void setConnectionState(VpnProtocol::VpnConnectionState state) override; + void setConnectionState(Vpn::ConnectionState state) override; protected: virtual void notify(Message type, const QString& title, @@ -26,7 +26,7 @@ protected: private: void showHideWindow(); - void setTrayState(VpnProtocol::VpnConnectionState state); + void setTrayState(Vpn::ConnectionState state); void onTrayActivated(QSystemTrayIcon::ActivationReason reason); void setTrayIcon(const QString &iconPath); diff --git a/client/ui/uilogic.cpp b/client/ui/uilogic.cpp index bb2f90b2..929d84d6 100644 --- a/client/ui/uilogic.cpp +++ b/client/ui/uilogic.cpp @@ -107,7 +107,7 @@ UiLogic::~UiLogic() emit hide(); #ifdef AMNEZIA_DESKTOP - if (m_vpnConnection->connectionState() != VpnProtocol::VpnConnectionState::Disconnected) { + if (m_vpnConnection->connectionState() != Vpn::ConnectionState::Disconnected) { m_vpnConnection->disconnectFromVpn(); for (int i = 0; i < 50; i++) { qApp->processEvents(QEventLoop::ExcludeUserInputEvents); @@ -131,13 +131,13 @@ void UiLogic::initializeUiLogic() #ifdef Q_OS_ANDROID connect(AndroidController::instance(), &AndroidController::initialized, [this](bool status, bool connected, const QDateTime& connectionDate) { if (connected) { - pageLogic()->onConnectionStateChanged(VpnProtocol::Connected); + pageLogic()->onConnectionStateChanged(Vpn::ConnectionState::Connected); m_vpnConnection->restoreConnection(); } }); if (!AndroidController::instance()->initialize(pageLogic())) { qCritical() << QString("Init failed") ; - emit VpnProtocol::Error; + emit Vpn::ConnectionState::Error; return; } #endif diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index 57d20127..468a6d96 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -50,12 +50,12 @@ void VpnConnection::onBytesChanged(quint64 receivedBytes, quint64 sentBytes) emit bytesChanged(receivedBytes, sentBytes); } -void VpnConnection::onConnectionStateChanged(VpnProtocol::VpnConnectionState state) +void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state) { #ifdef AMNEZIA_DESKTOP if (IpcClient::Interface()) { - if (state == VpnProtocol::Connected){ + if (state == Vpn::ConnectionState::Connected){ IpcClient::Interface()->resetIpStack(); IpcClient::Interface()->flushDns(); @@ -85,7 +85,7 @@ void VpnConnection::onConnectionStateChanged(VpnProtocol::VpnConnectionState sta } - else if (state == VpnProtocol::Error) { + else if (state == Vpn::ConnectionState::Error) { IpcClient::Interface()->flushDns(); if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) { @@ -96,7 +96,7 @@ void VpnConnection::onConnectionStateChanged(VpnProtocol::VpnConnectionState sta #endif #ifdef Q_OS_IOS - if (state == VpnProtocol::Connected){ + if (state == Vpn::ConnectionState::Connected){ m_isIOSConnected = true; checkIOSStatus(); } @@ -179,7 +179,7 @@ QSharedPointer VpnConnection::vpnProtocol() const void VpnConnection::addRoutes(const QStringList &ips) { #ifdef AMNEZIA_DESKTOP - if (connectionState() == VpnProtocol::Connected && IpcClient::Interface()) { + if (connectionState() == Vpn::ConnectionState::Connected && IpcClient::Interface()) { if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) { IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), ips); } @@ -193,7 +193,7 @@ void VpnConnection::addRoutes(const QStringList &ips) void VpnConnection::deleteRoutes(const QStringList &ips) { #ifdef AMNEZIA_DESKTOP - if (connectionState() == VpnProtocol::Connected && IpcClient::Interface()) { + if (connectionState() == Vpn::ConnectionState::Connected && IpcClient::Interface()) { if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) { IpcClient::Interface()->routeDeleteList(vpnProtocol()->vpnGateway(), ips); } @@ -319,14 +319,14 @@ void VpnConnection::connectToVpn(int serverIndex, if (!IpcClient::init(m_IpcClient)) { qWarning() << "Error occurred when init IPC client"; emit serviceIsNotReady(); - emit connectionStateChanged(VpnProtocol::Error); + emit connectionStateChanged(Vpn::ConnectionState::Error); return; } } #endif m_remoteAddress = credentials.hostName; - emit connectionStateChanged(VpnProtocol::Connecting); + emit connectionStateChanged(Vpn::ConnectionState::Connecting); if (m_vpnProtocol) { disconnect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError); @@ -338,14 +338,14 @@ void VpnConnection::connectToVpn(int serverIndex, m_vpnConfiguration = createVpnConfiguration(serverIndex, credentials, container, containerConfig); if (e) { - emit connectionStateChanged(VpnProtocol::Error); + emit connectionStateChanged(Vpn::ConnectionState::Error); return; } #if !defined (Q_OS_ANDROID) && !defined (Q_OS_IOS) m_vpnProtocol.reset(VpnProtocol::factory(container, m_vpnConfiguration)); if (!m_vpnProtocol) { - emit VpnProtocol::Error; + emit Vpn::ConnectionState::Error; return; } m_vpnProtocol->prepare(); @@ -362,7 +362,7 @@ void VpnConnection::connectToVpn(int serverIndex, // IOSVpnProtocol *iosVpnProtocol = new IOSVpnProtocol(proto, m_vpnConfiguration); if (!iosVpnProtocol->initialize()) { qDebug() << QString("Init failed") ; - emit VpnProtocol::Error; + emit Vpn::ConnectionState::Error; return; } m_vpnProtocol.reset(iosVpnProtocol); @@ -371,12 +371,12 @@ void VpnConnection::connectToVpn(int serverIndex, createProtocolConnections(); e = m_vpnProtocol.data()->start(); - if (e) emit VpnProtocol::Error; + if (e) emit Vpn::ConnectionState::Error; } void VpnConnection::createProtocolConnections() { connect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError); - connect(m_vpnProtocol.data(), SIGNAL(connectionStateChanged(VpnProtocol::VpnConnectionState)), this, SLOT(onConnectionStateChanged(VpnProtocol::VpnConnectionState))); + connect(m_vpnProtocol.data(), SIGNAL(connectionStateChanged(Vpn::ConnectionState)), this, SLOT(onConnectionStateChanged(Vpn::ConnectionState))); connect(m_vpnProtocol.data(), SIGNAL(bytesChanged(quint64, quint64)), this, SLOT(onBytesChanged(quint64, quint64))); } @@ -433,7 +433,7 @@ void VpnConnection::disconnectFromVpn() #endif if (!m_vpnProtocol.data()) { - emit connectionStateChanged(VpnProtocol::Disconnected); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); #ifdef Q_OS_ANDROID AndroidController::instance()->stop(); #endif @@ -442,9 +442,9 @@ void VpnConnection::disconnectFromVpn() m_vpnProtocol.data()->stop(); } -VpnProtocol::VpnConnectionState VpnConnection::connectionState() +Vpn::ConnectionState VpnConnection::connectionState() { - if (!m_vpnProtocol) return VpnProtocol::Disconnected; + if (!m_vpnProtocol) return Vpn::ConnectionState::Disconnected; return m_vpnProtocol->connectionState(); } diff --git a/client/vpnconnection.h b/client/vpnconnection.h index 8dea7478..3a3a3047 100644 --- a/client/vpnconnection.h +++ b/client/vpnconnection.h @@ -54,7 +54,7 @@ public: bool isConnected() const; bool isDisconnected() const; - VpnProtocol::VpnConnectionState connectionState(); + Vpn::ConnectionState connectionState(); QSharedPointer vpnProtocol() const; void addRoutes(const QStringList &ips); @@ -76,14 +76,14 @@ public slots: signals: void bytesChanged(quint64 receivedBytes, quint64 sentBytes); - void connectionStateChanged(VpnProtocol::VpnConnectionState state); + void connectionStateChanged(Vpn::ConnectionState state); void vpnProtocolError(amnezia::ErrorCode error); void serviceIsNotReady(); protected slots: void onBytesChanged(quint64 receivedBytes, quint64 sentBytes); - void onConnectionStateChanged(VpnProtocol::VpnConnectionState state); + void onConnectionStateChanged(Vpn::ConnectionState state); #ifdef Q_OS_IOS void checkIOSStatus(); From 03a0e2084ae35810821ec9910057f72cc9408d9d Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 15 May 2023 13:38:17 +0800 Subject: [PATCH 018/278] added PageLoader and pageController --- client/amnezia_application.cpp | 4 + client/amnezia_application.h | 3 + client/images/controls/download.svg | 5 + client/resources.qrc | 1 + client/ui/controllers/pageController.cpp | 25 +++++ client/ui/controllers/pageController.h | 50 ++++++++++ client/ui/models/servers_model.cpp | 8 +- client/ui/models/servers_model.h | 3 +- client/ui/qml/Components/ConnectButton.qml | 94 +++++++++---------- client/ui/qml/Controls2/DropDownType.qml | 5 + client/ui/qml/Controls2/Header2Type.qml | 2 + client/ui/qml/Controls2/HeaderType.qml | 2 + client/ui/qml/PageLoader.qml | 32 ++++++- client/ui/qml/Pages2/PageHome.qml | 84 +++++++++++++---- .../Pages2/PageSetupWizardConfigSource.qml | 6 +- .../qml/Pages2/PageSetupWizardCredentials.qml | 8 +- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 4 +- .../qml/Pages2/PageSetupWizardInstalling.qml | 4 +- .../PageSetupWizardProtocolSettings.qml | 6 +- .../qml/Pages2/PageSetupWizardProtocols.qml | 6 +- client/ui/qml/Pages2/PageSetupWizardStart.qml | 10 +- .../ui/qml/Pages2/PageSetupWizardTextKey.qml | 6 +- client/ui/qml/Pages2/PageStart.qml | 4 +- client/ui/qml/Pages2/PageTest.qml | 5 +- client/ui/qml/main2.qml | 73 +------------- client/ui/uilogic.cpp | 6 -- client/ui/uilogic.h | 2 - 27 files changed, 265 insertions(+), 193 deletions(-) create mode 100644 client/images/controls/download.svg create mode 100644 client/ui/controllers/pageController.cpp create mode 100644 client/ui/controllers/pageController.h diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index a8231fce..4b7f31b7 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -118,6 +118,9 @@ void AmneziaApplication::init() m_vpnConnection.get(), &VpnConnection::disconnectFromVpn, Qt::QueuedConnection); m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get()); + m_pageController.reset(new PageController(m_serversModel)); + m_engine->rootContext()->setContextProperty("PageController", m_pageController.get()); + // m_uiLogic->registerPagesLogic(); @@ -185,6 +188,7 @@ void AmneziaApplication::registerTypes() // Vpn::declareQmlVpnConnectionStateEnum(); + PageLoader::declareQmlPageEnum(); } void AmneziaApplication::loadFonts() diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 322a440c..e2113908 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -13,9 +13,11 @@ #include "ui/uilogic.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/pageController.h" #define amnApp (static_cast(QCoreApplication::instance())) @@ -66,6 +68,7 @@ private: QScopedPointer m_vpnConnection; QScopedPointer m_connectionController; + QScopedPointer m_pageController; }; diff --git a/client/images/controls/download.svg b/client/images/controls/download.svg new file mode 100644 index 00000000..1e592887 --- /dev/null +++ b/client/images/controls/download.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/resources.qrc b/client/resources.qrc index f75c8132..77e7d86f 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -226,5 +226,6 @@ images/connectionProgress.svg images/connectionOff.svg images/connectionOn.svg + images/controls/download.svg diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/pageController.cpp new file mode 100644 index 00000000..10120767 --- /dev/null +++ b/client/ui/controllers/pageController.cpp @@ -0,0 +1,25 @@ +#include "pageController.h" + +PageController::PageController(const QSharedPointer &serversModel, + QObject *parent) : QObject(parent), m_serversModel(serversModel) +{ +} + +void PageController::setStartPage() +{ + if (m_serversModel->getServersCount()) { + if (m_serversModel->getDefaultServerIndex() < 0) { + m_serversModel->setDefaultServerIndex(0); + } + emit goToPage(PageLoader::PageEnum::PageStart, false); + } else { + emit goToPage(PageLoader::PageEnum::PageSetupWizardStart, false); + } +} + +QString PageController::getPagePath(PageLoader::PageEnum page) +{ + QMetaEnum metaEnum = QMetaEnum::fromType(); + QString pageName = metaEnum.valueToKey(static_cast(page)); + return "Pages2/" + pageName + ".qml"; +} diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h new file mode 100644 index 00000000..9bfd6bda --- /dev/null +++ b/client/ui/controllers/pageController.h @@ -0,0 +1,50 @@ +#ifndef PAGECONTROLLER_H +#define PAGECONTROLLER_H + +#include +#include + +#include "ui/models/servers_model.h" + +namespace PageLoader +{ + Q_NAMESPACE + enum class PageEnum { PageStart = 0, PageHome, PageSettings, PageShare, + + PageSetupWizardStart, PageTest, PageSetupWizardCredentials, PageSetupWizardProtocols, PageSetupWizardEasy, + PageSetupWizardProtocolSettings, PageSetupWizardInstalling, PageSetupWizardConfigSource, + PageSetupWizardTextKey + }; + Q_ENUM_NS(PageEnum) + + static void declareQmlPageEnum() { + qmlRegisterUncreatableMetaObject( + PageLoader::staticMetaObject, + "PageEnum", + 1, 0, + "PageEnum", + "Error: only enums" + ); + } +} + +class PageController : public QObject +{ + Q_OBJECT +public: + explicit PageController(const QSharedPointer &serversModel, + QObject *parent = nullptr); + +public slots: + void setStartPage(); + QString getPagePath(PageLoader::PageEnum page); + +signals: + void goToPage(PageLoader::PageEnum page, bool slide = true); + void closePage(); + +private: + QSharedPointer m_serversModel; +}; + +#endif // PAGECONTROLLER_H diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index dccf59b9..8f39e140 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -56,6 +56,7 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const return QVariant(); } +//todo mode to setData? void ServersModel::setDefaultServerIndex(int index) { // beginResetModel(); @@ -63,11 +64,16 @@ void ServersModel::setDefaultServerIndex(int index) // endResetModel(); } -int ServersModel::getDefaultServerIndex() +const int ServersModel::getDefaultServerIndex() { return m_settings->defaultServerIndex(); } +const int ServersModel::getServersCount() +{ + return m_settings->serversCount(); +} + QHash ServersModel::roleNames() const { QHash roles; roles[DescRole] = "desc"; diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index 30c8b5a2..9fb5ac1a 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -31,7 +31,8 @@ public: public slots: void setDefaultServerIndex(int index); - int getDefaultServerIndex(); + const int getDefaultServerIndex(); + const int getServersCount(); protected: QHash roleNames() const override; diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml index bce652d8..8bf39f03 100644 --- a/client/ui/qml/Components/ConnectButton.qml +++ b/client/ui/qml/Components/ConnectButton.qml @@ -7,15 +7,8 @@ import ConnectionState 1.0 Button { id: root - property var isConnected: ConnectionController.isConnected - text: "Подключиться" -// implicitHeight: 190 -// implicitWidth: 190 - -// color: "transparent" - background: Image { id: border @@ -54,56 +47,55 @@ Button { onClicked: { ConnectionController.onConnectionButtonClicked() - console.log(connectionProccess.from) } Connections { target: ConnectionController function onConnectionStateChanged(state) { switch(state) { - case ConnectionState.Unknown: { - console.log("Unknown") - break - } - case ConnectionState.Disconnected: { - console.log("Disconnected") - connectionProccess.running = false - root.text = "Подключиться" - break - } - case ConnectionState.Preparing: { - console.log("Preparing") - break - } - case ConnectionState.Connecting: { - console.log("Connecting") - connectionProccess.running = true - root.text = "Подключение..." - break - } - case ConnectionState.Connected: { - console.log("Connected") - connectionProccess.running = false - root.text = "Подключено" - break - } - case ConnectionState.Disconnecting: { - console.log("Disconnecting") - connectionProccess.running = true - root.text = "Отключение..." - break - } - case ConnectionState.Reconnecting: { - console.log("Reconnecting") - connectionProccess.running = true - root.text = "Переподключение..." - break - } - case ConnectionState.Error: { - console.log("Error") - connectionProccess.running = false - break - } + case ConnectionState.Unknown: { + console.log("Unknown") + break + } + case ConnectionState.Disconnected: { + console.log("Disconnected") + connectionProccess.running = false + root.text = "Подключиться" + break + } + case ConnectionState.Preparing: { + console.log("Preparing") + break + } + case ConnectionState.Connecting: { + console.log("Connecting") + connectionProccess.running = true + root.text = "Подключение..." + break + } + case ConnectionState.Connected: { + console.log("Connected") + connectionProccess.running = false + root.text = "Подключено" + break + } + case ConnectionState.Disconnecting: { + console.log("Disconnecting") + connectionProccess.running = true + root.text = "Отключение..." + break + } + case ConnectionState.Reconnecting: { + console.log("Reconnecting") + connectionProccess.running = true + root.text = "Переподключение..." + break + } + case ConnectionState.Error: { + console.log("Error") + connectionProccess.running = false + break + } } } } diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 64013ba4..eab189f3 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -154,6 +154,10 @@ Item { anchors.topMargin: 16 anchors.leftMargin: 16 anchors.rightMargin: 16 + + backButtonFunction: function() { + root.menuVisible = false + } } FlickableType { @@ -190,6 +194,7 @@ Item { id: loader sourceComponent: root.menuDelegate property QtObject modelData: model + property var delegateIndex: index } } } diff --git a/client/ui/qml/Controls2/Header2Type.qml b/client/ui/qml/Controls2/Header2Type.qml index 5c2f0c9b..ef463acd 100644 --- a/client/ui/qml/Controls2/Header2Type.qml +++ b/client/ui/qml/Controls2/Header2Type.qml @@ -35,6 +35,8 @@ Item { onClicked: { if (backButtonFunction && typeof backButtonFunction === "function") { backButtonFunction() + } else { + PageController.closePage() } } } diff --git a/client/ui/qml/Controls2/HeaderType.qml b/client/ui/qml/Controls2/HeaderType.qml index a051d2d8..6c4e7847 100644 --- a/client/ui/qml/Controls2/HeaderType.qml +++ b/client/ui/qml/Controls2/HeaderType.qml @@ -35,6 +35,8 @@ Item { onClicked: { if (backButtonFunction && typeof backButtonFunction === "function") { backButtonFunction() + } else { + PageController.closePage() } } } diff --git a/client/ui/qml/PageLoader.qml b/client/ui/qml/PageLoader.qml index 86f9e5a1..b3d53a05 100644 --- a/client/ui/qml/PageLoader.qml +++ b/client/ui/qml/PageLoader.qml @@ -3,5 +3,35 @@ import QtQuick.Controls StackView { id: stackView - initialItem: "PageSetupWizardStart" + + function gotoPage(page, slide) { + if (slide) { + stackView.push(PageController.getPagePath(page), {}, StackView.PushTransition) + } else { + stackView.push(PageController.getPagePath(page), {}, StackView.Immediate) + } + } + + function closePage() { + if (stackView.depth <= 1) { + return + } + + stackView.pop() + } + + Connections { + target: PageController + function onGoToPage(page, slide) { + stackView.gotoPage(page, slide) + } + + function onClosePage() { + stackView.closePage() + } + } + + Component.onCompleted: { + PageController.setStartPage() + } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 5dd0aaae..8edd6b12 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -8,15 +8,13 @@ import PageEnum 1.0 import ProtocolEnum 1.0 import "./" -import "../Pages" import "../Controls2" import "../Controls2/TextTypes" import "../Config" import "../Components" -PageBase { +Item { id: root - page: PageEnum.PageHome property string defaultColor: "#1C1D21" @@ -143,10 +141,6 @@ PageBase { ValueFilter { roleName: "serviceType" value: ProtocolEnum.Vpn - }, - ValueFilter { - roleName: "isInstalled" - value: true } ] } @@ -196,6 +190,19 @@ PageBase { indicator: Rectangle { anchors.fill: parent color: containerRadioButton.hovered ? "#2C2D30" : "#1C1D21" + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + + checkable: { + if (modelData !== null) { + if (modelData.isInstalled) { + return true + } + } + return false } RowLayout { @@ -207,10 +214,33 @@ PageBase { z: 1 + Image { + source: { + if (modelData !== null) { + if (modelData.isInstalled) { + return "qrc:/images/controls/check.svg" + } + } + return "qrc:/images/controls/download.svg" + } + visible: { + if (modelData !== null) { + if (modelData.isInstalled) { + return containerRadioButton.checked + } + } + return true + } + + width: 24 + height: 24 + + Layout.rightMargin: 8 + } + Text { id: containerRadioButtonText - // todo remove dirty hack? text: { if (modelData !== null) { return modelData.name @@ -228,22 +258,26 @@ PageBase { Layout.topMargin: 20 Layout.bottomMargin: 20 } - - Image { - source: "qrc:/images/controls/check.svg" - visible: containerRadioButton.checked - width: 24 - height: 24 - - Layout.rightMargin: 8 - } } onClicked: { - modelData.isDefault = true + if (checked) { + modelData.isDefault = true - containersDropDown.text = containerRadioButtonText.text - containersDropDown.menuVisible = false + containersDropDown.text = containerRadioButtonText.text + containersDropDown.menuVisible = false + } else { + ContainersModel.setCurrentlyInstalledContainerIndex(proxyContainersModel.mapToSource(delegateIndex)) + PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) + containersDropDown.menuVisible = false + menu.visible = false + } + } + + MouseArea { + anchors.fill: containerRadioButton + cursorShape: Qt.PointingHandCursor + enabled: false } } @@ -326,6 +360,10 @@ PageBase { indicator: Rectangle { anchors.fill: parent color: serverRadioButton.hovered ? "#2C2D30" : "#1C1D21" + + Behavior on color { + PropertyAnimation { duration: 200 } + } } RowLayout { @@ -370,6 +408,12 @@ PageBase { ServersModel.setDefaultServerIndex(index) ContainersModel.setSelectedServerIndex(index) } + + MouseArea { + anchors.fill: serverRadioButton + cursorShape: Qt.PointingHandCursor + enabled: false + } } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 5445d1f3..db2730ac 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -6,14 +6,12 @@ import QtQuick.Dialogs import PageEnum 1.0 import "./" -import "../Pages" import "../Controls2" import "../Controls2/TextTypes" import "../Config" -PageBase { +Item { id: root - page: PageEnum.PageSetupWizardInstalling FlickableType { id: fl @@ -100,7 +98,7 @@ PageBase { iconImage: "qrc:/images/controls/text-cursor.svg" onClickedFunc: function() { - UiLogic.goToPage(PageEnum.PageSetupWizardTextKey) + PageController.goToPage(PageEnum.PageSetupWizardTextKey) } } Rectangle { diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index 506c1cde..479e4bfa 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -5,13 +5,11 @@ import QtQuick.Layouts import PageEnum 1.0 import "./" -import "../Pages" import "../Controls2" import "../Config" -PageBase { +Item { id: root - page: PageEnum.PageSetupWizardCredentials FlickableType { id: fl @@ -61,7 +59,7 @@ PageBase { text: qsTr("Настроить сервер простым образом") onClicked: function() { - UiLogic.goToPage(PageEnum.PageSetupWizardEasy) + PageController.goToPage(PageEnum.PageSetupWizardEasy) } } @@ -79,7 +77,7 @@ PageBase { text: qsTr("Выбрать протокол для установки") onClicked: function() { - UiLogic.goToPage(PageEnum.PageSetupWizardProtocols) + PageController.goToPage(PageEnum.PageSetupWizardProtocols) } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index 94e22fe3..664f4de7 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -5,13 +5,11 @@ import QtQuick.Layouts import PageEnum 1.0 import "./" -import "../Pages" import "../Controls2" import "../Config" -PageBase { +Item { id: root - page: PageEnum.PageSetupWizardEasy FlickableType { id: fl diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index a7418257..fa0d5a14 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -5,14 +5,12 @@ import QtQuick.Layouts import PageEnum 1.0 import "./" -import "../Pages" import "../Controls2" import "../Controls2/TextTypes" import "../Config" -PageBase { +Item { id: root - page: PageEnum.PageSetupWizardInstalling FlickableType { id: fl diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 78856444..6c3b64b9 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -5,14 +5,12 @@ import QtQuick.Layouts import PageEnum 1.0 import "./" -import "../Pages" import "../Controls2" import "../Controls2/TextTypes" import "../Config" -PageBase { +Item { id: root - page: PageEnum.PageSetupWizardProtocolSettings FlickableType { id: fl @@ -90,7 +88,7 @@ PageBase { text: qsTr("Установить") onClicked: function() { - UiLogic.goToPage(PageEnum.PageSetupWizardInstalling) + PageController.goToPage(PageEnum.PageSetupWizardInstalling) } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index 50107615..24a86a07 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -8,13 +8,11 @@ import PageEnum 1.0 import ProtocolEnum 1.0 import "./" -import "../Pages" import "../Controls2" import "../Config" -PageBase { +Item { id: root - page: PageEnum.PageSetupWizardProtocols SortFilterProxyModel { id: proxyContainersModel @@ -89,7 +87,7 @@ PageBase { onClickedFunc: function() { ContainersModel.setCurrentlyInstalledContainerIndex(proxyContainersModel.mapToSource(index)) - UiLogic.goToPage(PageEnum.PageSetupWizardProtocolSettings) + PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index 0167ed1f..5f1a0479 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -5,14 +5,12 @@ import QtQuick.Layouts import PageEnum 1.0 import "./" -import "../Pages" import "../Controls2" import "../Config" import "../Controls2/TextTypes" -PageBase { +Item { id: root - page: PageEnum.PageSetupWizardStart FlickableType { id: fl @@ -77,7 +75,7 @@ PageBase { text: qsTr("У меня ничего нет") onClicked: { - UiLogic.goToPage(PageEnum.PageTest) + PageController.goToPage(PageEnum.PageTest) } } } @@ -132,7 +130,7 @@ PageBase { buttonImage: "qrc:/images/controls/chevron-right.svg" onClickedFunc: function() { - UiLogic.goToPage(PageEnum.PageSetupWizardCredentials) + PageController.goToPage(PageEnum.PageSetupWizardCredentials) drawer.visible = false } } @@ -148,7 +146,7 @@ PageBase { buttonImage: "qrc:/images/controls/chevron-right.svg" onClickedFunc: function() { - UiLogic.goToPage(PageEnum.PageSetupWizardConfigSource) + PageController.goToPage(PageEnum.PageSetupWizardConfigSource) drawer.visible = false } } diff --git a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml index d295f1b9..f94b9ff4 100644 --- a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml +++ b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml @@ -5,14 +5,12 @@ import QtQuick.Layouts import PageEnum 1.0 import "./" -import "../Pages" import "../Controls2" import "../Controls2/TextTypes" import "../Config" -PageBase { +Item { id: root - page: PageEnum.PageSetupWizardInstalling FlickableType { id: fl @@ -68,7 +66,7 @@ PageBase { text: qsTr("Подключиться") onClicked: function() { -// UiLogic.goToPage(PageEnum.PageSetupWizardInstalling) +// PageController.goToPage(PageEnum.PageSetupWizardInstalling) } } } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 220c6f19..ade0980d 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -5,14 +5,12 @@ import QtQuick.Layouts import PageEnum 1.0 import "./" -import "../Pages" import "../Controls2" import "../Controls2/TextTypes" import "../Config" -PageBase { +Item { id: root - page: PageEnum.PageStart StackLayout { id: stackLayout diff --git a/client/ui/qml/Pages2/PageTest.qml b/client/ui/qml/Pages2/PageTest.qml index 2336e46e..53ec99fc 100644 --- a/client/ui/qml/Pages2/PageTest.qml +++ b/client/ui/qml/Pages2/PageTest.qml @@ -4,15 +4,12 @@ import QtQuick.Layouts import PageEnum 1.0 import "./" -import "../Pages" import "../Controls2" import "../Config" import "../Controls2/TextTypes" -PageBase { +Item { id: root - page: PageEnum.Test - logic: ViewConfigLogic ColumnLayout { id: content diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index 6261e9a7..b10d749f 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -22,83 +22,14 @@ Window { title: "AmneziaVPN" - function gotoPage(page, reset, slide) { - if (pageStackView.depth > 0) { - pageStackView.currentItem.deactivated() - } - - if (slide) { - pageStackView.push(UiLogic.pageEnumToString(page), {}, StackView.PushTransition) - } else { - pageStackView.push(UiLogic.pageEnumToString(page), {}, StackView.Immediate) - } - - pageStackView.currentItem.activated(reset) - } - - function closePage() { - if (pageStackView.depth <= 1) { - return - } - pageStackView.currentItem.deactivated() - pageStackView.pop() - } - - function setStartPage(page, slide) { - if (pageStackView.depth > 0) { - pageStackView.currentItem.deactivated() - } - - pageStackView.clear() - if (slide) { - pageStackView.push(UiLogic.pageEnumToString(page), {}, StackView.PushTransition) - } else { - pageStackView.push(UiLogic.pageEnumToString(page), {}, StackView.Immediate) - } - if (page === PageEnum.Start) { - UiLogic.pushButtonBackFromStartVisible = !pageStackView.empty - UiLogic.onUpdatePage(); - } - } - Rectangle { anchors.fill: parent color: "#0E0E11" } - StackView { - id: pageStackView + PageLoader { + id: pageLoader anchors.fill: parent focus: true } - - Connections { - target: UiLogic - function onGoToPage(page, reset, slide) { - root.gotoPage(page, reset, slide) - } - - function onClosePage() { - root.closePage() - } - - function onSetStartPage(page, slide) { - root.setStartPage(page, slide) - } - - function onShow() { - root.show() - UiLogic.initializeUiLogic() - } - - function onHide() { - root.hide() - } - - function onRaise() { - root.show() - root.raise() - root.requestActivate() - } - } } diff --git a/client/ui/uilogic.cpp b/client/ui/uilogic.cpp index 929d84d6..3ad74645 100644 --- a/client/ui/uilogic.cpp +++ b/client/ui/uilogic.cpp @@ -617,9 +617,3 @@ bool UiLogic::isContainerAlreadyAddedToGui(DockerContainer container) } return false; } - -QString UiLogic::pageEnumToString(Page page) { - QMetaEnum metaEnum = QMetaEnum::fromType(); - QString pageName = metaEnum.valueToKey(static_cast(page)); - return "Pages2/" + pageName + ".qml"; -} diff --git a/client/ui/uilogic.h b/client/ui/uilogic.h index 4b392066..081b8e5a 100644 --- a/client/ui/uilogic.h +++ b/client/ui/uilogic.h @@ -123,8 +123,6 @@ public: Q_INVOKABLE amnezia::ErrorCode addAlreadyInstalledContainersGui(bool &isServerCreated); - Q_INVOKABLE QString pageEnumToString(PageEnumNS::Page page); - void shareTempFile(const QString &suggestedName, QString ext, const QString& data); static QString getOpenFileName(QWidget *parent = nullptr, const QString &caption = QString(), From acca85b99a1bf3d0cc71e5508dc1b1edee71dad5 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 17 May 2023 23:28:27 +0800 Subject: [PATCH 019/278] added installController with logic for server/container installation --- client/amnezia_application.cpp | 3 + client/resources.qrc | 1 + .../ui/controllers/connectionController.cpp | 2 +- client/ui/controllers/installController.cpp | 82 ++++++++ client/ui/controllers/installController.h | 40 ++++ client/ui/models/containers_model.cpp | 53 +++-- client/ui/models/containers_model.h | 16 +- client/ui/models/servers_model.cpp | 21 +- client/ui/models/servers_model.h | 9 +- client/ui/qml/Components/ConnectButton.qml | 2 + client/ui/qml/Controls2/DropDownType.qml | 64 +++--- client/ui/qml/Controls2/Header2Type.qml | 4 +- client/ui/qml/Controls2/ProgressBarType.qml | 19 ++ client/ui/qml/Pages2/PageHome.qml | 39 ++-- .../qml/Pages2/PageSetupWizardCredentials.qml | 10 + .../qml/Pages2/PageSetupWizardInstalling.qml | 105 ++++++++-- .../PageSetupWizardProtocolSettings.qml | 194 +++++++++++++----- .../qml/Pages2/PageSetupWizardProtocols.qml | 4 - client/ui/qml/Pages2/PageSetupWizardStart.qml | 2 +- 19 files changed, 519 insertions(+), 151 deletions(-) create mode 100644 client/ui/controllers/installController.cpp create mode 100644 client/ui/controllers/installController.h create mode 100644 client/ui/qml/Controls2/ProgressBarType.qml diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 4b7f31b7..df4a4ec1 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -121,6 +121,9 @@ void AmneziaApplication::init() m_pageController.reset(new PageController(m_serversModel)); m_engine->rootContext()->setContextProperty("PageController", m_pageController.get()); + m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_settings)); + m_engine->rootContext()->setContextProperty("InstallController", m_installController.get()); + // m_uiLogic->registerPagesLogic(); diff --git a/client/resources.qrc b/client/resources.qrc index 77e7d86f..a50a23c5 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -227,5 +227,6 @@ images/connectionOff.svg images/connectionOn.svg images/controls/download.svg + ui/qml/Controls2/ProgressBarType.qml diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index ce52a2c8..58fef373 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -33,7 +33,7 @@ bool ConnectionController::openVpnConnection() DockerContainer container = m_containersModel->getDefaultContainer(); QModelIndex containerModelIndex = m_containersModel->index(container); const QJsonObject &containerConfig = qvariant_cast(m_containersModel->data(containerModelIndex, - ContainersModel::ContainersModelRoles::ConfigRole)); + ContainersModel::Roles::ConfigRole)); //todo error handling qApp->processEvents(); diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp new file mode 100644 index 00000000..ae753382 --- /dev/null +++ b/client/ui/controllers/installController.cpp @@ -0,0 +1,82 @@ +#include "installController.h" + +#include + +#include "core/servercontroller.h" + +InstallController::InstallController(const QSharedPointer &serversModel, + const QSharedPointer &containersModel, + const std::shared_ptr &settings, + QObject *parent) : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings) +{ + +} + +ErrorCode InstallController::install(DockerContainer container, int port, TransportProto transportProto) +{ + Proto mainProto = ContainerProps::defaultProtocol(container); + + QJsonObject containerConfig { + { config_key::port, QString::number(port) }, + { config_key::transport_proto, ProtocolProps::transportProtoToString(transportProto, mainProto) } + }; + QJsonObject config { + { config_key::container, ContainerProps::containerToString(container) }, + { ProtocolProps::protoToString(mainProto), containerConfig } + }; + + if (m_shouldCreateServer) { + return installServer(container, config); + } else { + return installContainer(container, config); + } +} + +ErrorCode InstallController::installServer(DockerContainer container, QJsonObject& config) +{ + //todo check if container already installed + ServerController serverController(m_settings); + ErrorCode errorCode = serverController.setupContainer(m_currentlyInstalledServerCredentials, container, config); + if (errorCode == ErrorCode::NoError) { + QJsonObject server; + server.insert(config_key::hostName, m_currentlyInstalledServerCredentials.hostName); + server.insert(config_key::userName, m_currentlyInstalledServerCredentials.userName); + server.insert(config_key::password, m_currentlyInstalledServerCredentials.password); + server.insert(config_key::port, m_currentlyInstalledServerCredentials.port); + server.insert(config_key::description, m_settings->nextAvailableServerName()); + + server.insert(config_key::containers, QJsonArray{ config }); + server.insert(config_key::defaultContainer, ContainerProps::containerToString(container)); + + m_settings->addServer(server); + m_settings->setDefaultServer(m_settings->serversCount() - 1); + } + + return errorCode; +} + +ErrorCode InstallController::installContainer(DockerContainer container, QJsonObject& config) +{ + //todo check if container already installed + ServerCredentials serverCredentials = m_serversModel->getCurrentlyProcessedServerCredentials(); + + ServerController serverController(m_settings); + ErrorCode errorCode = serverController.setupContainer(serverCredentials, container, config); + if (errorCode == ErrorCode::NoError) { + m_containersModel->setData(m_containersModel->index(container), config, ContainersModel::Roles::ConfigRole); + emit installContainerFinished(); + } + + //todo error processing + return errorCode; +} + +void InstallController::setCurrentlyInstalledServerCredentials(const QString &hostName, const QString &userName, const QString &password, const int &port) +{ + m_currentlyInstalledServerCredentials = { hostName, userName, password, port }; +} + +void InstallController::setShouldCreateServer(bool shouldCreateServer) +{ + m_shouldCreateServer = shouldCreateServer; +} diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h new file mode 100644 index 00000000..f9cd9fb0 --- /dev/null +++ b/client/ui/controllers/installController.h @@ -0,0 +1,40 @@ +#ifndef INSTALLCONTROLLER_H +#define INSTALLCONTROLLER_H + +#include + +#include "core/defs.h" +#include "containers/containers_defs.h" +#include "ui/models/servers_model.h" +#include "ui/models/containers_model.h" + +class InstallController : public QObject +{ + Q_OBJECT +public: + explicit InstallController(const QSharedPointer &serversModel, + const QSharedPointer &containersModel, + const std::shared_ptr &settings, + QObject *parent = nullptr); + +public slots: + ErrorCode install(DockerContainer container, int port, TransportProto transportProto); + void setCurrentlyInstalledServerCredentials(const QString &hostName, const QString &userName, const QString &password, const int &port); + void setShouldCreateServer(bool shouldCreateServer); + +signals: + void installContainerFinished(); +private: + ErrorCode installServer(DockerContainer container, QJsonObject& config); + ErrorCode installContainer(DockerContainer container, QJsonObject& config); + + QSharedPointer m_serversModel; + QSharedPointer m_containersModel; + std::shared_ptr m_settings; + + ServerCredentials m_currentlyInstalledServerCredentials; + + bool m_shouldCreateServer; +}; + +#endif // INSTALLCONTROLLER_H diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index db7572e6..a814a838 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -2,7 +2,6 @@ ContainersModel::ContainersModel(std::shared_ptr settings, QObject *parent) : m_settings(settings), QAbstractListModel(parent) { - setSelectedServerIndex(m_settings->defaultServerIndex()); } int ContainersModel::rowCount(const QModelIndex &parent) const @@ -17,9 +16,23 @@ bool ContainersModel::setData(const QModelIndex &index, const QVariant &value, i return false; } - if (role == IsDefaultRole) { - DockerContainer container = ContainerProps::allContainers().at(index.row()); - m_settings->setDefaultContainer(m_selectedServerIndex, container); + DockerContainer container = ContainerProps::allContainers().at(index.row()); + + switch (role) { + case NameRole: +// return ContainerProps::containerHumanNames().value(container); + case DescRole: +// return ContainerProps::containerDescriptions().value(container); + case ConfigRole: + m_settings->setContainerConfig(m_currentlyProcessedServerIndex, container, value.toJsonObject()); + case ServiceTypeRole: +// return ContainerProps::containerService(container); + case DockerContainerRole: +// return container; + case IsInstalledRole: +// return m_settings->containers(m_currentlyProcessedServerIndex).contains(container); + case IsDefaultRole: + m_settings->setDefaultContainer(m_currentlyProcessedServerIndex, container); } emit dataChanged(index, index); @@ -41,40 +54,42 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const case DescRole: return ContainerProps::containerDescriptions().value(container); case ConfigRole: - return m_settings->containerConfig(m_selectedServerIndex, container); + return m_settings->containerConfig(m_currentlyProcessedServerIndex, container); case ServiceTypeRole: return ContainerProps::containerService(container); + case DockerContainerRole: + return container; case IsInstalledRole: - return m_settings->containers(m_selectedServerIndex).contains(container); + return m_settings->containers(m_currentlyProcessedServerIndex).contains(container); + case IsCurrentlyInstalled: + return container == static_cast(m_currentlyInstalledContainerIndex); case IsDefaultRole: - return container == m_settings->defaultContainer(m_selectedServerIndex); + return container == m_settings->defaultContainer(m_currentlyProcessedServerIndex); } return QVariant(); } -void ContainersModel::setSelectedServerIndex(int index) +void ContainersModel::setCurrentlyProcessedServerIndex(int index) { beginResetModel(); - m_selectedServerIndex = index; + m_currentlyProcessedServerIndex = index; endResetModel(); } void ContainersModel::setCurrentlyInstalledContainerIndex(int index) { -// beginResetModel(); - m_currentlyInstalledContainerIndex = createIndex(index, 0); -// endResetModel(); -} - -QString ContainersModel::getCurrentlyInstalledContainerName() -{ - return data(m_currentlyInstalledContainerIndex, NameRole).toString(); + m_currentlyInstalledContainerIndex = index; } DockerContainer ContainersModel::getDefaultContainer() { - return m_settings->defaultContainer(m_selectedServerIndex); + return m_settings->defaultContainer(m_currentlyProcessedServerIndex); +} + +int ContainersModel::getCurrentlyInstalledContainerIndex() +{ + return m_currentlyInstalledContainerIndex; } QHash ContainersModel::roleNames() const { @@ -82,7 +97,9 @@ QHash ContainersModel::roleNames() const { roles[NameRole] = "name"; roles[DescRole] = "description"; roles[ServiceTypeRole] = "serviceType"; + roles[DockerContainerRole] = "dockerContainer"; roles[IsInstalledRole] = "isInstalled"; + roles[IsCurrentlyInstalled] = "isCurrentlyInstalled"; roles[IsDefaultRole] = "isDefault"; return roles; } diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index c56511db..15012925 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -15,12 +15,14 @@ class ContainersModel : public QAbstractListModel public: ContainersModel(std::shared_ptr settings, QObject *parent = nullptr); public: - enum ContainersModelRoles { + enum Roles { NameRole = Qt::UserRole + 1, DescRole, ServiceTypeRole, ConfigRole, + DockerContainerRole, IsInstalledRole, + IsCurrentlyInstalled, IsDefaultRole }; @@ -28,20 +30,20 @@ public: bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - Q_INVOKABLE void setSelectedServerIndex(int index); - Q_INVOKABLE void setCurrentlyInstalledContainerIndex(int index); - - Q_INVOKABLE QString getCurrentlyInstalledContainerName(); public slots: DockerContainer getDefaultContainer(); + void setCurrentlyProcessedServerIndex(int index); + void setCurrentlyInstalledContainerIndex(int index); + int getCurrentlyInstalledContainerIndex(); + protected: QHash roleNames() const override; private: - int m_selectedServerIndex; - QModelIndex m_currentlyInstalledContainerIndex; + int m_currentlyProcessedServerIndex; + int m_currentlyInstalledContainerIndex; std::shared_ptr m_settings; }; diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 8f39e140..fe973811 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -38,14 +38,14 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const const QJsonObject server = servers.at(index.row()).toObject(); switch (role) { - case DescRole: { + case NameRole: { auto description = server.value(config_key::description).toString(); if (description.isEmpty()) { return server.value(config_key::hostName).toString(); } return description; } - case AddressRole: + case HostNameRole: return server.value(config_key::hostName).toString(); case CredentialsRole: return QVariant::fromValue(m_settings->serverCredentials(index.row())); @@ -74,10 +74,21 @@ const int ServersModel::getServersCount() return m_settings->serversCount(); } +void ServersModel::setCurrentlyProcessedServerIndex(int index) +{ + m_currenlyProcessedServerIndex = index; +} + +ServerCredentials ServersModel::getCurrentlyProcessedServerCredentials() +{ + return qvariant_cast(data(index(m_currenlyProcessedServerIndex), CredentialsRole)); +} + QHash ServersModel::roleNames() const { QHash roles; - roles[DescRole] = "desc"; - roles[AddressRole] = "address"; - roles[IsDefaultRole] = "is_default"; + roles[NameRole] = "name"; + roles[HostNameRole] = "hostName"; + roles[CredentialsRole] = "credentials"; + roles[IsDefaultRole] = "isDefault"; return roles; } diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index 9fb5ac1a..08b44976 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -16,8 +16,8 @@ class ServersModel : public QAbstractListModel Q_OBJECT public: enum ServersModelRoles { - DescRole = Qt::UserRole + 1, - AddressRole, + NameRole = Qt::UserRole + 1, + HostNameRole, CredentialsRole, IsDefaultRole }; @@ -34,11 +34,16 @@ public slots: const int getDefaultServerIndex(); const int getServersCount(); + void setCurrentlyProcessedServerIndex(int index); + ServerCredentials getCurrentlyProcessedServerCredentials(); + protected: QHash roleNames() const override; private: std::shared_ptr m_settings; + + int m_currenlyProcessedServerIndex; }; #endif // SERVERSMODEL_H diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml index 8bf39f03..5002d5bd 100644 --- a/client/ui/qml/Components/ConnectButton.qml +++ b/client/ui/qml/Components/ConnectButton.qml @@ -65,6 +65,8 @@ Button { } case ConnectionState.Preparing: { console.log("Preparing") + connectionProccess.running = true + root.text = "Подключение..." break } case ConnectionState.Connecting: { diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index eab189f3..e2a3531f 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -8,40 +8,38 @@ Item { id: root property string text + property string textColor: "#d7d8db" + property string descriptionText property string headerText property string headerBackButtonImage - property var onClickedFunc - property string buttonImage: "qrc:/images/controls/chevron-down.svg" - property string buttonImageColor: "#494B50" + property var onRootButtonClicked + property string rootButtonImage: "qrc:/images/controls/chevron-down.svg" + property string rootButtonImageColor: "#494B50" + property string rootButtonDefaultColor: "#1C1D21" + property int rootButtonMaximumWidth - property int buttonMaximumWidth - - property string defaultColor: "#1C1D21" - - property string textColor: "#d7d8db" - - property string borderColor: "#494B50" - property int borderWidth: 1 + property string rootButtonBorderColor: "#494B50" + property int rootButtonBorderWidth: 1 property Component menuDelegate property variant menuModel property alias menuVisible: menu.visible - implicitWidth: buttonContent.implicitWidth - implicitHeight: buttonContent.implicitHeight + implicitWidth: rootButtonContent.implicitWidth + implicitHeight: rootButtonContent.implicitHeight Rectangle { - id: buttonBackground - anchors.fill: buttonContent + id: rootButtonBackground + anchors.fill: rootButtonContent radius: 16 - color: defaultColor - border.color: borderColor - border.width: borderWidth + color: rootButtonDefaultColor + border.color: rootButtonBorderColor + border.width: rootButtonBorderWidth Behavior on border.width { PropertyAnimation { duration: 200 } @@ -49,7 +47,7 @@ Item { } RowLayout { - id: buttonContent + id: rootButtonContent anchors.fill: parent spacing: 0 @@ -71,7 +69,7 @@ Item { horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter - Layout.maximumWidth: buttonMaximumWidth ? buttonMaximumWidth : implicitWidth + Layout.maximumWidth: rootButtonMaximumWidth ? rootButtonMaximumWidth : implicitWidth color: root.textColor text: root.text @@ -81,38 +79,36 @@ Item { } } + //todo change to image type ImageButtonType { - id: button - Layout.leftMargin: 4 Layout.rightMargin: 16 hoverEnabled: false - image: buttonImage - imageColor: buttonImageColor - onClicked: { - if (onClickedFunc && typeof onClickedFunc === "function") { - onClickedFunc() - } - } + image: rootButtonImage + imageColor: rootButtonImageColor } } MouseArea { - anchors.fill: buttonContent + anchors.fill: rootButtonContent cursorShape: Qt.PointingHandCursor hoverEnabled: true onEntered: { - buttonBackground.border.width = borderWidth + rootButtonBackground.border.width = rootButtonBorderWidth } onExited: { - buttonBackground.border.width = 0 + rootButtonBackground.border.width = 0 } onClicked: { - menu.visible = true + if (onRootButtonClicked && typeof onRootButtonClicked === "function") { + onRootButtonClicked() + } else { + menu.visible = true + } } } @@ -132,7 +128,7 @@ Item { radius: 16 color: "#1C1D21" - border.color: borderColor + border.color: "#494B50" border.width: 1 } diff --git a/client/ui/qml/Controls2/Header2Type.qml b/client/ui/qml/Controls2/Header2Type.qml index ef463acd..f985d523 100644 --- a/client/ui/qml/Controls2/Header2Type.qml +++ b/client/ui/qml/Controls2/Header2Type.qml @@ -59,8 +59,8 @@ Item { visible: image ? true : false onClicked: { - if (actionButtonImage && typeof actionButtonImage === "function") { - actionButtonImage() + if (actionButtonFunction && typeof actionButtonFunction === "function") { + actionButtonFunction() } } } diff --git a/client/ui/qml/Controls2/ProgressBarType.qml b/client/ui/qml/Controls2/ProgressBarType.qml new file mode 100644 index 00000000..f2f2370a --- /dev/null +++ b/client/ui/qml/Controls2/ProgressBarType.qml @@ -0,0 +1,19 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +ProgressBar { + id: root + + implicitHeight: 4 + + background: Rectangle { + color: "#412102" + } + + contentItem: Rectangle { + width: root.visualPosition * root.width + height: root.height + color: "#FBB26A" + } +} diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 8edd6b12..a29a4089 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -20,8 +20,10 @@ Item { property string borderColor: "#2C2D30" - property string currentServerName: serversMenuContent.currentItem.delegateData.desc - property string currentServerDescription: serversMenuContent.currentItem.delegateData.address + property string currentServerName: serversMenuContent.currentItem.delegateData.name + property string currentServerHostName: serversMenuContent.currentItem.delegateData.hostName + + property string currentContainerName ConnectButton { anchors.centerIn: parent @@ -72,7 +74,7 @@ Item { Layout.bottomMargin: 44 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - text: currentServerDescription + text: currentContainerName + " | " + currentServerHostName } } @@ -127,7 +129,7 @@ Item { Layout.bottomMargin: 24 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - text: currentServerDescription + text: currentServerHostName } RowLayout { @@ -150,16 +152,21 @@ Item { implicitHeight: 40 - borderWidth: 0 - buttonImageColor: "#0E0E11" - buttonMaximumWidth: 150 //todo make it dynamic - - defaultColor: "#D7D8DB" + rootButtonBorderWidth: 0 + rootButtonImageColor: "#0E0E11" + rootButtonMaximumWidth: 150 //todo make it dynamic + rootButtonDefaultColor: "#D7D8DB" textColor: "#0E0E11" headerText: "Протокол подключения" headerBackButtonImage: "qrc:/images/controls/arrow-left.svg" + onRootButtonClicked: function() { + ServersModel.setCurrentlyProcessedServerIndex(serversMenuContent.currentIndex) + ContainersModel.setCurrentlyProcessedServerIndex(serversMenuContent.currentIndex) + containersDropDown.menuVisible = true + } + menuModel: proxyContainersModel ButtonGroup { @@ -265,9 +272,11 @@ Item { modelData.isDefault = true containersDropDown.text = containerRadioButtonText.text + root.currentContainerName = containerRadioButtonText.text containersDropDown.menuVisible = false } else { ContainersModel.setCurrentlyInstalledContainerIndex(proxyContainersModel.mapToSource(delegateIndex)) + InstallController.setShouldCreateServer(false) PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) containersDropDown.menuVisible = false menu.visible = false @@ -307,6 +316,10 @@ Item { actionButtonImage: "qrc:/images/controls/plus.svg" headerText: "Серверы" + + actionButtonFunction: function() { + PageController.goToPage(PageEnum.PageSetupWizardStart) + } } } @@ -378,7 +391,7 @@ Item { Text { id: serverRadioButtonText - text: desc + text: name color: "#D7D8DB" font.pixelSize: 16 font.weight: 400 @@ -402,11 +415,11 @@ Item { } onClicked: { - root.currentServerName = desc - root.currentServerDescription = address + serversMenuContent.currentIndex = index + root.currentServerName = name + root.currentServerHostName = hostName ServersModel.setDefaultServerIndex(index) - ContainersModel.setSelectedServerIndex(index) } MouseArea { diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index 479e4bfa..def76b89 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -38,18 +38,25 @@ Item { } TextFieldWithHeaderType { + id: hostname + Layout.fillWidth: true headerText: "Server IP adress [:port]" } TextFieldWithHeaderType { + id: username + Layout.fillWidth: true headerText: "Login to connect via SSH" } TextFieldWithHeaderType { + id: secretData + Layout.fillWidth: true headerText: "Password / Private key" + textField.echoMode: TextInput.Password } BasicButtonType { @@ -77,6 +84,9 @@ Item { text: qsTr("Выбрать протокол для установки") onClicked: function() { + InstallController.setShouldCreateServer(true) + InstallController.setCurrentlyInstalledServerCredentials(hostname.textField.text, username.textField.text, secretData.textField.text) + PageController.goToPage(PageEnum.PageSetupWizardProtocols) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index fa0d5a14..c2c761cc 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -2,6 +2,8 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import SortFilterProxyModel 0.2 + import PageEnum 1.0 import "./" @@ -12,32 +14,111 @@ import "../Config" Item { id: root + property real progressBarValue: 0 + + SortFilterProxyModel { + id: proxyContainersModel + sourceModel: ContainersModel + filters: [ + ValueFilter { + roleName: "isCurrentlyInstalled" + value: true + } + ] + } + FlickableType { id: fl - anchors.top: root.top - anchors.bottom: root.bottom + anchors.fill: parent contentHeight: content.height - ColumnLayout { + Column { id: content anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 spacing: 16 - HeaderType { - Layout.fillWidth: true - Layout.topMargin: 20 + ListView { + // todo change id naming + id: container + width: parent.width + height: container.contentItem.height + currentIndex: -1 + clip: true + interactive: false + model: proxyContainersModel - //TODO remove later - backButtonImage: "qrc:/images/controls/arrow-left.svg" + delegate: Item { + implicitWidth: container.width + implicitHeight: delegateContent.implicitHeight - headerText: "Установка" - descriptionText: ContainersModel.getCurrentlyInstalledContainerName() + ColumnLayout { + id: delegateContent + + anchors.fill: parent + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + HeaderType { + Layout.fillWidth: true + Layout.topMargin: 20 + + headerText: "Установка" + descriptionText: name + } + + ProgressBarType { + id: progressBar + + Layout.fillWidth: true + Layout.topMargin: 32 + + value: progressBarValue + + Timer { + id: timer + + interval: 300 + repeat: true + running: true + onTriggered: { + progressBarValue += 0.001 + } + } + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 8 + + text: "Обычно это занимает не больше 5 минут" + } + } + } + } + } + + Timer { + id: closePageTimer + + interval: 1000 + repeat: false + running: false + onTriggered: { + // todo go to root installing page + PageController.goToPage(PageEnum.PageHome) + } + } + + Connections { + target: InstallController + + function onInstallContainerFinished() { + progressBarValue = 1 + closePageTimer.start() } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 6c3b64b9..9499f5a5 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -2,7 +2,11 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import SortFilterProxyModel 0.2 + import PageEnum 1.0 +import ContainerProps 1.0 +import ProtocolProps 1.0 import "./" import "../Controls2" @@ -12,83 +16,169 @@ import "../Config" Item { id: root + SortFilterProxyModel { + id: proxyContainersModel + sourceModel: ContainersModel + filters: [ + ValueFilter { + roleName: "isCurrentlyInstalled" + value: true + } + ] + } + FlickableType { id: fl - anchors.top: root.top - anchors.bottom: root.bottom + anchors.fill: parent contentHeight: content.height - ColumnLayout { + Column { id: content anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 spacing: 16 - HeaderType { - Layout.fillWidth: true - Layout.topMargin: 20 + ListView { + // todo change id naming + id: containers + width: parent.width + height: containers.contentItem.height + currentIndex: -1 + clip: true + interactive: false + model: proxyContainersModel - backButtonImage: "qrc:/images/controls/arrow-left.svg" + delegate: Item { + implicitWidth: containers.width + implicitHeight: delegateContent.implicitHeight - headerText: "Установка " + ContainersModel.getCurrentlyInstalledContainerName() - descriptionText: "Эти настройки можно будет изменить позже" - } + ColumnLayout { + id: delegateContent - ParagraphTextType { - Layout.topMargin: 16 + anchors.fill: parent + anchors.rightMargin: 16 + anchors.leftMargin: 16 - text: "Network protocol" - } + HeaderType { + id: header - //TODO move to separete control - Rectangle { - implicitWidth: buttonGroup.implicitWidth - implicitHeight: buttonGroup.implicitHeight + Layout.fillWidth: true + Layout.topMargin: 20 - color: "#1C1D21" - radius: 16 + backButtonImage: "qrc:/images/controls/arrow-left.svg" - RowLayout { - id: buttonGroup + headerText: "Установка " + name + descriptionText: "Эти настройки можно будет изменить позже" + } - spacing: 0 + ParagraphTextType { + id: transportProtoHeader - HorizontalRadioButton { - implicitWidth: (root.width - 32) / 2 - text: "UDP" - } + Layout.topMargin: 16 - HorizontalRadioButton { - implicitWidth: (root.width - 32) / 2 - text: "TCP" + text: "Network protocol" + } + + Rectangle { + id: transportProtoBackground + + implicitWidth: transportProtoButtonGroup.implicitWidth + implicitHeight: transportProtoButtonGroup.implicitHeight + + color: "#1C1D21" + radius: 16 + + RowLayout { + id: transportProtoButtonGroup + + property int currentIndex + spacing: 0 + + HorizontalRadioButton { + checked: transportProtoButtonGroup.currentIndex === 0 + + implicitWidth: (root.width - 32) / 2 + text: "UDP" + + hoverEnabled: !transportProtoButtonMouseArea.enabled + + onClicked: { + transportProtoButtonGroup.currentIndex = 0 + } + } + + HorizontalRadioButton { + checked: transportProtoButtonGroup.currentIndex === 1 + + implicitWidth: (root.width - 32) / 2 + text: "TCP" + + hoverEnabled: !transportProtoButtonMouseArea.enabled + + onClicked: { + transportProtoButtonGroup.currentIndex = 1 + } + } + } + + MouseArea { + id: transportProtoButtonMouseArea + + anchors.fill: parent + } + } + + TextFieldWithHeaderType { + id: port + + Layout.fillWidth: true + headerText: "Port" + } + + Rectangle { + // todo make it dynamic + implicitHeight: root.height - port.implicitHeight - + transportProtoBackground.implicitHeight - transportProtoHeader.implicitHeight - + header.implicitHeight - installButton.implicitHeight - 100 + + color: "transparent" + } + + BasicButtonType { + id: installButton + + Layout.fillWidth: true + Layout.bottomMargin: 32 + + text: qsTr("Установить") + + onClicked: function() { + PageController.goToPage(PageEnum.PageSetupWizardInstalling); + + InstallController.install(dockerContainer, port.textFieldText, transportProtoButtonGroup.currentIndex) + } + } + + Component.onCompleted: { + //todo move to protocols model? + var defaultContainerProto = ContainerProps.defaultProtocol(dockerContainer) + + if (ProtocolProps.defaultPort(defaultContainerProto) < 0) { + port.visible = false + } else { + port.textFieldText = ProtocolProps.defaultPort(defaultContainerProto) + } + transportProtoButtonGroup.currentIndex = ProtocolProps.defaultTransportProto(defaultContainerProto) + + port.enabled = ProtocolProps.defaultPortChangeable(defaultContainerProto) + transportProtoButtonMouseArea.enabled = !ProtocolProps.defaultTransportProtoChangeable(defaultContainerProto) + } } } } - - TextFieldWithHeaderType { - Layout.fillWidth: true - headerText: "Port" - } - } - } - - BasicButtonType { - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 - anchors.bottomMargin: 32 - - text: qsTr("Установить") - - onClicked: function() { - PageController.goToPage(PageEnum.PageSetupWizardInstalling) } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index 24a86a07..11b09edd 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -18,10 +18,6 @@ Item { id: proxyContainersModel sourceModel: ContainersModel filters: [ - ValueFilter { - roleName: "is_installed_role" - value: false - }, ValueFilter { roleName: "service_type_role" value: ProtocolEnum.Vpn diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index 5f1a0479..8293b210 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -96,7 +96,7 @@ Item { radius: 16 color: "#1C1D21" - border.color: borderColor + border.color: "#2C2D30" border.width: 1 } From 04791139494edf5ee9240cb01041afa2d4822068 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 22 May 2023 00:10:51 +0800 Subject: [PATCH 020/278] moved ContainersPageHomeListView and ConnectionTypeSelectionDrawer to separate components --- client/amnezia_application.h | 2 + client/containers/containers_defs.cpp | 30 ++++ client/containers/containers_defs.h | 4 + client/core/defs.h | 4 +- client/core/sshclient.cpp | 8 +- client/resources.qrc | 2 + client/settings.cpp | 4 +- client/ui/controllers/installController.cpp | 16 +- client/ui/controllers/installController.h | 2 +- client/ui/models/containers_model.cpp | 24 ++- client/ui/models/containers_model.h | 16 +- client/ui/pages_logic/StartPageLogic.cpp | 8 +- .../ConnectionTypeSelectionDrawer.qml | 87 +++++++++ .../Components/ContainersPageHomeListView.qml | 119 +++++++++++++ client/ui/qml/Controls2/DropDownType.qml | 30 +--- client/ui/qml/Pages2/PageHome.qml | 168 +++--------------- .../qml/Pages2/PageSetupWizardCredentials.qml | 5 +- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 109 +++++++++--- .../qml/Pages2/PageSetupWizardInstalling.qml | 2 +- .../PageSetupWizardProtocolSettings.qml | 1 - .../qml/Pages2/PageSetupWizardProtocols.qml | 11 +- client/ui/qml/Pages2/PageSetupWizardStart.qml | 81 +-------- client/ui/uilogic.cpp | 4 +- 23 files changed, 443 insertions(+), 294 deletions(-) create mode 100644 client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml create mode 100644 client/ui/qml/Components/ContainersPageHomeListView.qml diff --git a/client/amnezia_application.h b/client/amnezia_application.h index e2113908..97b16f13 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -18,6 +18,7 @@ #include "ui/models/containers_model.h" #include "ui/controllers/connectionController.h" #include "ui/controllers/pageController.h" +#include "ui/controllers/installController.h" #define amnApp (static_cast(QCoreApplication::instance())) @@ -69,6 +70,7 @@ private: QScopedPointer m_connectionController; QScopedPointer m_pageController; + QScopedPointer m_installController; }; diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index cca77e7d..7c7c000b 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -188,3 +188,33 @@ QStringList ContainerProps::fixedPortsForContainer(DockerContainer c) default: return {}; } } + +bool ContainerProps::isEasySetupContainer(DockerContainer container) +{ + switch (container) { + case DockerContainer::OpenVpn : return true; + case DockerContainer::Cloak : return true; + case DockerContainer::ShadowSocks : return true; + default: return false; + } +} + +QString ContainerProps::easySetupHeader(DockerContainer container) +{ + switch (container) { + case DockerContainer::OpenVpn : return tr("Low"); + case DockerContainer::Cloak : return tr("High"); + case DockerContainer::ShadowSocks : return tr("Medium"); + default: return ""; + } +} + +QString ContainerProps::easySetupDescription(DockerContainer container) +{ + switch (container) { + case DockerContainer::OpenVpn : return tr("Many foreign websites and VPN providers are blocked"); + case DockerContainer::Cloak : return tr("Some foreign sites are blocked, but VPN providers are not blocked"); + case DockerContainer::ShadowSocks : return tr("I just want to increase the level of privacy"); + default: return ""; + } +} diff --git a/client/containers/containers_defs.h b/client/containers/containers_defs.h index ff230c3e..d4a35d26 100644 --- a/client/containers/containers_defs.h +++ b/client/containers/containers_defs.h @@ -57,6 +57,10 @@ public: Q_INVOKABLE static bool isSupportedByCurrentPlatform(amnezia::DockerContainer c); Q_INVOKABLE static QStringList fixedPortsForContainer(amnezia::DockerContainer c); + + static bool isEasySetupContainer(amnezia::DockerContainer container); + static QString easySetupHeader(amnezia::DockerContainer container); + static QString easySetupDescription(amnezia::DockerContainer container); }; diff --git a/client/core/defs.h b/client/core/defs.h index 452038a5..3a3ff565 100644 --- a/client/core/defs.h +++ b/client/core/defs.h @@ -12,10 +12,10 @@ struct ServerCredentials { QString hostName; QString userName; - QString password; + QString secretData; int port = 22; - bool isValid() const { return !hostName.isEmpty() && !userName.isEmpty() && !password.isEmpty() && port > 0; } + bool isValid() const { return !hostName.isEmpty() && !userName.isEmpty() && !secretData.isEmpty() && port > 0; } }; enum ErrorCode diff --git a/client/core/sshclient.cpp b/client/core/sshclient.cpp index 795af965..0367dc1f 100644 --- a/client/core/sshclient.cpp +++ b/client/core/sshclient.cpp @@ -55,10 +55,10 @@ namespace libssh { std::string authUsername = credentials.userName.toStdString(); int authResult = SSH_ERROR; - if (credentials.password.contains("BEGIN") && credentials.password.contains("PRIVATE KEY")) { + if (credentials.secretData.contains("BEGIN") && credentials.secretData.contains("PRIVATE KEY")) { ssh_key privateKey = nullptr; ssh_key publicKey = nullptr; - authResult = ssh_pki_import_privkey_base64(credentials.password.toStdString().c_str(), nullptr, callback, nullptr, &privateKey); + authResult = ssh_pki_import_privkey_base64(credentials.secretData.toStdString().c_str(), nullptr, callback, nullptr, &privateKey); if (authResult == SSH_OK) { authResult = ssh_pki_export_privkey_to_pubkey(privateKey, &publicKey); } @@ -86,7 +86,7 @@ namespace libssh { return errorCode; } } else { - authResult = ssh_userauth_password(m_session, authUsername.c_str(), credentials.password.toStdString().c_str()); + authResult = ssh_userauth_password(m_session, authUsername.c_str(), credentials.secretData.toStdString().c_str()); if (authResult != SSH_OK) { qDebug() << ssh_get_error(m_session); return fromLibsshErrorCode(ssh_get_error_code(m_session)); @@ -354,7 +354,7 @@ namespace libssh { ssh_key privateKey = nullptr; m_passphraseCallback = passphraseCallback; - authResult = ssh_pki_import_privkey_base64(credentials.password.toStdString().c_str(), nullptr, callback, nullptr, &privateKey); + authResult = ssh_pki_import_privkey_base64(credentials.secretData.toStdString().c_str(), nullptr, callback, nullptr, &privateKey); if (authResult == SSH_OK) { char* key = new char[65535]; diff --git a/client/resources.qrc b/client/resources.qrc index a50a23c5..ff7139a8 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -228,5 +228,7 @@ images/connectionOn.svg images/controls/download.svg ui/qml/Controls2/ProgressBarType.qml + ui/qml/Components/ConnectionTypeSelectionDrawer.qml + ui/qml/Components/ContainersPageHomeListView.qml diff --git a/client/settings.cpp b/client/settings.cpp index 135e87ba..a978d408 100644 --- a/client/settings.cpp +++ b/client/settings.cpp @@ -185,7 +185,7 @@ bool Settings::haveAuthData(int serverIndex) const { if (serverIndex < 0) return false; ServerCredentials cred = serverCredentials(serverIndex); - return (!cred.hostName.isEmpty() && !cred.userName.isEmpty() && !cred.password.isEmpty()); + return (!cred.hostName.isEmpty() && !cred.userName.isEmpty() && !cred.secretData.isEmpty()); } QString Settings::nextAvailableServerName() const @@ -321,7 +321,7 @@ ServerCredentials Settings::serverCredentials(int index) const ServerCredentials credentials; credentials.hostName = s.value(config_key::hostName).toString(); credentials.userName = s.value(config_key::userName).toString(); - credentials.password = s.value(config_key::password).toString(); + credentials.secretData = s.value(config_key::password).toString(); credentials.port = s.value(config_key::port).toInt(); return credentials; diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index ae753382..3e4809e6 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -41,7 +41,7 @@ ErrorCode InstallController::installServer(DockerContainer container, QJsonObjec QJsonObject server; server.insert(config_key::hostName, m_currentlyInstalledServerCredentials.hostName); server.insert(config_key::userName, m_currentlyInstalledServerCredentials.userName); - server.insert(config_key::password, m_currentlyInstalledServerCredentials.password); + server.insert(config_key::password, m_currentlyInstalledServerCredentials.secretData); server.insert(config_key::port, m_currentlyInstalledServerCredentials.port); server.insert(config_key::description, m_settings->nextAvailableServerName()); @@ -50,8 +50,12 @@ ErrorCode InstallController::installServer(DockerContainer container, QJsonObjec m_settings->addServer(server); m_settings->setDefaultServer(m_settings->serversCount() - 1); + + //todo change to server finished + emit installContainerFinished(); } + //todo error processing return errorCode; } @@ -71,9 +75,15 @@ ErrorCode InstallController::installContainer(DockerContainer container, QJsonOb return errorCode; } -void InstallController::setCurrentlyInstalledServerCredentials(const QString &hostName, const QString &userName, const QString &password, const int &port) +void InstallController::setCurrentlyInstalledServerCredentials(const QString &hostName, const QString &userName, const QString &secretData) { - m_currentlyInstalledServerCredentials = { hostName, userName, password, port }; + m_currentlyInstalledServerCredentials.hostName = hostName; + if (m_currentlyInstalledServerCredentials.hostName.contains(":")) { + m_currentlyInstalledServerCredentials.port = m_currentlyInstalledServerCredentials.hostName.split(":").at(1).toInt(); + m_currentlyInstalledServerCredentials.hostName = m_currentlyInstalledServerCredentials.hostName.split(":").at(0); + } + m_currentlyInstalledServerCredentials.userName = userName; + m_currentlyInstalledServerCredentials.secretData = secretData; } void InstallController::setShouldCreateServer(bool shouldCreateServer) diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index f9cd9fb0..c0d6ad20 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -19,7 +19,7 @@ public: public slots: ErrorCode install(DockerContainer container, int port, TransportProto transportProto); - void setCurrentlyInstalledServerCredentials(const QString &hostName, const QString &userName, const QString &password, const int &port); + void setCurrentlyInstalledServerCredentials(const QString &hostName, const QString &userName, const QString &secretData); void setShouldCreateServer(bool shouldCreateServer); signals: diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index a814a838..6a999011 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -33,6 +33,7 @@ bool ContainersModel::setData(const QModelIndex &index, const QVariant &value, i // return m_settings->containers(m_currentlyProcessedServerIndex).contains(container); case IsDefaultRole: m_settings->setDefaultContainer(m_currentlyProcessedServerIndex, container); + emit defaultContainerChanged(); } emit dataChanged(index, index); @@ -59,12 +60,20 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const return ContainerProps::containerService(container); case DockerContainerRole: return container; + case IsEasySetupContainerRole: + return ContainerProps::isEasySetupContainer(container); + case EasySetupHeaderRole: + return ContainerProps::easySetupHeader(container); + case EasySetupDescriptionRole: + return ContainerProps::easySetupDescription(container); case IsInstalledRole: return m_settings->containers(m_currentlyProcessedServerIndex).contains(container); - case IsCurrentlyInstalled: + case IsCurrentlyInstalledRole: return container == static_cast(m_currentlyInstalledContainerIndex); case IsDefaultRole: return container == m_settings->defaultContainer(m_currentlyProcessedServerIndex); + case IsSupportedRole: + return ContainerProps::isSupportedByCurrentPlatform(container); } return QVariant(); @@ -87,6 +96,11 @@ DockerContainer ContainersModel::getDefaultContainer() return m_settings->defaultContainer(m_currentlyProcessedServerIndex); } +QString ContainersModel::getDefaultContainerName() +{ + return ContainerProps::containerHumanNames().value(getDefaultContainer()); +} + int ContainersModel::getCurrentlyInstalledContainerIndex() { return m_currentlyInstalledContainerIndex; @@ -98,8 +112,14 @@ QHash ContainersModel::roleNames() const { roles[DescRole] = "description"; roles[ServiceTypeRole] = "serviceType"; roles[DockerContainerRole] = "dockerContainer"; + + roles[IsEasySetupContainerRole] = "isEasySetupContainer"; + roles[EasySetupHeaderRole] = "easySetupHeader"; + roles[EasySetupDescriptionRole] = "easySetupDescription"; + roles[IsInstalledRole] = "isInstalled"; - roles[IsCurrentlyInstalled] = "isCurrentlyInstalled"; + roles[IsCurrentlyInstalledRole] = "isCurrentlyInstalled"; roles[IsDefaultRole] = "isDefault"; + roles[IsSupportedRole] = "isSupported"; return roles; } diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index 15012925..36b5567a 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -14,16 +14,22 @@ class ContainersModel : public QAbstractListModel Q_OBJECT public: ContainersModel(std::shared_ptr settings, QObject *parent = nullptr); -public: + enum Roles { NameRole = Qt::UserRole + 1, DescRole, ServiceTypeRole, ConfigRole, DockerContainerRole, + + IsEasySetupContainerRole, + EasySetupHeaderRole, + EasySetupDescriptionRole, + IsInstalledRole, - IsCurrentlyInstalled, - IsDefaultRole + IsCurrentlyInstalledRole, + IsDefaultRole, + IsSupportedRole }; int rowCount(const QModelIndex &parent = QModelIndex()) const override; @@ -31,8 +37,12 @@ public: bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; +signals: + void defaultContainerChanged(); + public slots: DockerContainer getDefaultContainer(); + QString getDefaultContainerName(); void setCurrentlyProcessedServerIndex(int index); void setCurrentlyInstalledContainerIndex(int index); diff --git a/client/ui/pages_logic/StartPageLogic.cpp b/client/ui/pages_logic/StartPageLogic.cpp index 8d9c33f6..86d972d1 100644 --- a/client/ui/pages_logic/StartPageLogic.cpp +++ b/client/ui/pages_logic/StartPageLogic.cpp @@ -123,9 +123,9 @@ void StartPageLogic::onPushButtonConnect() key = m_configurator->sshConfigurator->convertOpenSShKey(key); } - serverCredentials.password = key; + serverCredentials.secretData = key; } else { - serverCredentials.password = lineEditPasswordText(); + serverCredentials.secretData = lineEditPasswordText(); } set_pushButtonConnectEnabled(false); @@ -147,7 +147,7 @@ void StartPageLogic::onPushButtonConnect() QString decryptedPrivateKey; errorCode = serverController.getDecryptedPrivateKey(serverCredentials, decryptedPrivateKey, passphraseCallback); if (errorCode == ErrorCode::NoError) { - serverCredentials.password = decryptedPrivateKey; + serverCredentials.secretData = decryptedPrivateKey; } } @@ -220,7 +220,7 @@ bool StartPageLogic::importConnection(const QJsonObject &profile) credentials.hostName = profile.value(config_key::hostName).toString(); credentials.port = profile.value(config_key::port).toInt(); credentials.userName = profile.value(config_key::userName).toString(); - credentials.password = profile.value(config_key::password).toString(); + credentials.secretData = profile.value(config_key::password).toString(); if (credentials.isValid() || profile.contains(config_key::containers)) { // check config diff --git a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml new file mode 100644 index 00000000..3ae5b278 --- /dev/null +++ b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml @@ -0,0 +1,87 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +Drawer { + id: root + + edge: Qt.BottomEdge + width: parent.width + height: parent.height * 0.4375 + + clip: true + modal: true + + background: Rectangle { + anchors.fill: parent + anchors.bottomMargin: -radius + radius: 16 + color: "#1C1D21" + + border.color: "#2C2D30" + border.width: 1 + } + + Overlay.modal: Rectangle { + color: Qt.rgba(14/255, 14/255, 17/255, 0.8) + } + + ColumnLayout { + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + Header2TextType { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.alignment: Qt.AlignHCenter + + text: "Данные для подключения" + wrapMode: Text.WordWrap + } + + LabelWithButtonType { + id: ip + Layout.fillWidth: true + Layout.topMargin: 32 + + text: "IP, логин и пароль от сервера" + buttonImage: "qrc:/images/controls/chevron-right.svg" + + onClickedFunc: function() { + PageController.goToPage(PageEnum.PageSetupWizardCredentials) + root.visible = false + } + } + Rectangle { + Layout.fillWidth: true + height: 1 + color: "#2C2D30" + } + LabelWithButtonType { + Layout.fillWidth: true + + text: "QR-код, ключ или файл настроек" + buttonImage: "qrc:/images/controls/chevron-right.svg" + + onClickedFunc: function() { + PageController.goToPage(PageEnum.PageSetupWizardConfigSource) + root.visible = false + } + } + Rectangle { + Layout.fillWidth: true + height: 1 + color: "#2C2D30" + } + } +} diff --git a/client/ui/qml/Components/ContainersPageHomeListView.qml b/client/ui/qml/Components/ContainersPageHomeListView.qml new file mode 100644 index 00000000..b3d34b62 --- /dev/null +++ b/client/ui/qml/Components/ContainersPageHomeListView.qml @@ -0,0 +1,119 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 + +import "../Controls2" +import "../Controls2/TextTypes" + + +ListView { + id: menuContent + + property var rootWidth + + width: rootWidth + height: menuContent.contentItem.height + + clip: true + + ButtonGroup { + id: containersRadioButtonGroup + } + + delegate: Item { + implicitWidth: rootWidth + implicitHeight: containerRadioButton.implicitHeight + + RadioButton { + id: containerRadioButton + + implicitWidth: parent.width + implicitHeight: containerRadioButtonContent.implicitHeight + + hoverEnabled: true + + ButtonGroup.group: containersRadioButtonGroup + + checked: isDefault + + indicator: Rectangle { + anchors.fill: parent + color: containerRadioButton.hovered ? "#2C2D30" : "#1C1D21" + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + + checkable: isInstalled + + RowLayout { + id: containerRadioButtonContent + anchors.fill: parent + + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + z: 1 + + Image { + source: isInstalled ? "qrc:/images/controls/check.svg" : "qrc:/images/controls/download.svg" + visible: isInstalled ? containerRadioButton.checked : true + + width: 24 + height: 24 + + Layout.rightMargin: 8 + } + + Text { + id: containerRadioButtonText + + text: name + color: "#D7D8DB" + font.pixelSize: 16 + font.weight: 400 + font.family: "PT Root UI VF" + + height: 24 + + Layout.fillWidth: true + Layout.topMargin: 20 + Layout.bottomMargin: 20 + } + } + + onClicked: { + if (checked) { + isDefault = true + menuContent.currentIndex = index + containersDropDown.menuVisible = false + } else { + ContainersModel.setCurrentlyInstalledContainerIndex(proxyContainersModel.mapToSource(index)) + InstallController.setShouldCreateServer(false) + PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) + containersDropDown.menuVisible = false + menu.visible = false + } + } + + MouseArea { + anchors.fill: containerRadioButton + cursorShape: Qt.PointingHandCursor + enabled: false + } + } + + Component.onCompleted: { + if (isDefault) { + root.currentContainerName = name + } + } + } +} + diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index e2a3531f..2b8986b5 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -24,8 +24,7 @@ Item { property string rootButtonBorderColor: "#494B50" property int rootButtonBorderWidth: 1 - property Component menuDelegate - property variant menuModel + property Component listView property alias menuVisible: menu.visible @@ -169,30 +168,9 @@ Item { spacing: 16 - ButtonGroup { - id: radioButtonGroup - } - - ListView { - id: menuContent - width: parent.width - height: menuContent.contentItem.height - - currentIndex: -1 - - clip: true - interactive: false - - model: root.menuModel - - delegate: Row { - Loader { - id: loader - sourceComponent: root.menuDelegate - property QtObject modelData: model - property var delegateIndex: index - } - } + Loader { + id: listViewLoader + sourceComponent: root.listView } } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index a29a4089..a04b1744 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -6,6 +6,7 @@ import SortFilterProxyModel 0.2 import PageEnum 1.0 import ProtocolEnum 1.0 +import ContainerProps 1.0 import "./" import "../Controls2" @@ -22,21 +23,28 @@ Item { property string currentServerName: serversMenuContent.currentItem.delegateData.name property string currentServerHostName: serversMenuContent.currentItem.delegateData.hostName - property string currentContainerName ConnectButton { anchors.centerIn: parent } + Connections { + target: ContainersModel + + function onDefaultContainerChanged() { + root.currentContainerName = ContainersModel.getDefaultContainerName() + } + } + Rectangle { id: buttonBackground anchors.fill: buttonContent anchors.bottomMargin: -radius radius: 16 - color: defaultColor - border.color: borderColor + color: root.defaultColor + border.color: root.borderColor border.width: 1 Rectangle { @@ -44,7 +52,7 @@ Item { height: 1 y: parent.height - height - parent.radius - color: borderColor + color: root.borderColor } } @@ -59,7 +67,7 @@ Item { Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Header1TextType { - text: currentServerName + text: root.currentServerName } Image { @@ -74,7 +82,7 @@ Item { Layout.bottomMargin: 44 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - text: currentContainerName + " | " + currentServerHostName + text: root.currentContainerName + " | " + root.currentServerHostName } } @@ -104,7 +112,7 @@ Item { radius: 16 color: "#1C1D21" - border.color: borderColor + border.color: root.borderColor border.width: 1 } @@ -122,14 +130,14 @@ Item { Layout.topMargin: 24 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - text: currentServerName + text: root.currentServerName } LabelTextType { Layout.bottomMargin: 24 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - text: currentServerHostName + text: root.currentServerHostName } RowLayout { @@ -157,6 +165,7 @@ Item { rootButtonMaximumWidth: 150 //todo make it dynamic rootButtonDefaultColor: "#D7D8DB" + text: root.currentContainerName textColor: "#0E0E11" headerText: "Протокол подключения" headerBackButtonImage: "qrc:/images/controls/arrow-left.svg" @@ -167,134 +176,11 @@ Item { containersDropDown.menuVisible = true } - menuModel: proxyContainersModel + listView: ContainersPageHomeListView { + rootWidth: root.width - ButtonGroup { - id: containersRadioButtonGroup - } - - menuDelegate: Item { - implicitWidth: root.width - implicitHeight: containerRadioButton.implicitHeight - - RadioButton { - id: containerRadioButton - - implicitWidth: parent.width - implicitHeight: containerRadioButtonContent.implicitHeight - - hoverEnabled: true - - ButtonGroup.group: containersRadioButtonGroup - - checked: { - if (modelData !== null) { - return modelData.isDefault - } - return false - } - - indicator: Rectangle { - anchors.fill: parent - color: containerRadioButton.hovered ? "#2C2D30" : "#1C1D21" - - Behavior on color { - PropertyAnimation { duration: 200 } - } - } - - checkable: { - if (modelData !== null) { - if (modelData.isInstalled) { - return true - } - } - return false - } - - RowLayout { - id: containerRadioButtonContent - anchors.fill: parent - - anchors.rightMargin: 16 - anchors.leftMargin: 16 - - z: 1 - - Image { - source: { - if (modelData !== null) { - if (modelData.isInstalled) { - return "qrc:/images/controls/check.svg" - } - } - return "qrc:/images/controls/download.svg" - } - visible: { - if (modelData !== null) { - if (modelData.isInstalled) { - return containerRadioButton.checked - } - } - return true - } - - width: 24 - height: 24 - - Layout.rightMargin: 8 - } - - Text { - id: containerRadioButtonText - - text: { - if (modelData !== null) { - return modelData.name - } else - return "" - } - color: "#D7D8DB" - font.pixelSize: 16 - font.weight: 400 - font.family: "PT Root UI VF" - - height: 24 - - Layout.fillWidth: true - Layout.topMargin: 20 - Layout.bottomMargin: 20 - } - } - - onClicked: { - if (checked) { - modelData.isDefault = true - - containersDropDown.text = containerRadioButtonText.text - root.currentContainerName = containerRadioButtonText.text - containersDropDown.menuVisible = false - } else { - ContainersModel.setCurrentlyInstalledContainerIndex(proxyContainersModel.mapToSource(delegateIndex)) - InstallController.setShouldCreateServer(false) - PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) - containersDropDown.menuVisible = false - menu.visible = false - } - } - - MouseArea { - anchors.fill: containerRadioButton - cursorShape: Qt.PointingHandCursor - enabled: false - } - } - - Component.onCompleted: { - if (modelData !== null && modelData.isDefault) { - containersDropDown.text = modelData.name - } - } + model: proxyContainersModel + currentIndex: ContainersModel.getDefaultContainer() } } @@ -318,9 +204,14 @@ Item { headerText: "Серверы" actionButtonFunction: function() { - PageController.goToPage(PageEnum.PageSetupWizardStart) + menu.visible = false + connectionTypeSelection.visible = true } } + + ConnectionTypeSelectionDrawer { + id: connectionTypeSelection + } } FlickableType { @@ -416,10 +307,9 @@ Item { onClicked: { serversMenuContent.currentIndex = index - root.currentServerName = name - root.currentServerHostName = hostName ServersModel.setDefaultServerIndex(index) + ContainersModel.setCurrentlyProcessedServerIndex(index) } MouseArea { diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index def76b89..20c6e735 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -41,7 +41,7 @@ Item { id: hostname Layout.fillWidth: true - headerText: "Server IP adress [:port]" + headerText: "Server IP address [:port]" } TextFieldWithHeaderType { @@ -66,6 +66,9 @@ Item { text: qsTr("Настроить сервер простым образом") onClicked: function() { + InstallController.setShouldCreateServer(true) + InstallController.setCurrentlyInstalledServerCredentials(hostname.textField.text, username.textField.text, secretData.textField.text) + PageController.goToPage(PageEnum.PageSetupWizardEasy) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index 664f4de7..9f53a240 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -2,7 +2,11 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import SortFilterProxyModel 0.2 + import PageEnum 1.0 +import ContainerProps 1.0 +import ProtocolProps 1.0 import "./" import "../Controls2" @@ -11,13 +15,28 @@ import "../Config" Item { id: root + SortFilterProxyModel { + id: proxyContainersModel + sourceModel: ContainersModel + filters: [ + ValueFilter { + roleName: "isEasySetupContainer" + value: true + } + ] + sorters: RoleSorter { + roleName: "dockerContainer" + sortOrder: Qt.DescendingOrder + } + } + FlickableType { id: fl anchors.top: root.top anchors.bottom: root.bottom contentHeight: content.height - ColumnLayout { + Column { id: content anchors.top: parent.top @@ -25,47 +44,91 @@ Item { anchors.right: parent.right anchors.rightMargin: 16 anchors.leftMargin: 16 + anchors.topMargin: 20 spacing: 16 HeaderType { - Layout.fillWidth: true - Layout.topMargin: 20 + implicitWidth: parent.width + anchors.topMargin: 20 backButtonImage: "qrc:/images/controls/arrow-left.svg" - headerText: "Какой уровень контроля интернета в вашем регионе?" + headerText: qsTr("What is the level of Internet control in your region?") } - CardType { - Layout.fillWidth: true + ListView { + id: containers + width: parent.width + height: containers.contentItem.height + spacing: 16 - headerText: "Высокий" - bodyText: "Многие иностранные сайты и VPN-провайдеры заблокированы" - } + currentIndex: 1 + clip: true + interactive: false + model: proxyContainersModel - CardType { - Layout.fillWidth: true + property int dockerContainer + property int containerDefaultPort + property int containerDefaultTransportProto - checked: true + delegate: Item { + implicitWidth: containers.width + implicitHeight: delegateContent.implicitHeight - headerText: "Средний" - bodyText: "Некоторые иностранные сайты заблокированы, но VPN-провайдеры не блокируются" - } + ColumnLayout { + id: delegateContent - CardType { - Layout.fillWidth: true + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right - headerText: "Низкий" - bodyText: "Хочу просто повысить уровень приватности" + CardType { + id: card + + Layout.fillWidth: true + + headerText: easySetupHeader + bodyText: easySetupDescription + + ButtonGroup.group: buttonGroup + + onClicked: function() { + var defaultContainerProto = ContainerProps.defaultProtocol(dockerContainer) + + containers.dockerContainer = dockerContainer + containers.containerDefaultPort = ProtocolProps.defaultPort(defaultContainerProto) + containers.containerDefaultTransportProto = ProtocolProps.defaultTransportProto(defaultContainerProto) + } + } + } + + Component.onCompleted: { + if (index === containers.currentIndex) { + card.checked = true + card.clicked() + } + } + } + + ButtonGroup { + id: buttonGroup + } } BasicButtonType { - Layout.fillWidth: true - Layout.topMargin: 24 - Layout.bottomMargin: 32 + implicitWidth: parent.width + anchors.topMargin: 24 + anchors.bottomMargin: 32 - text: qsTr("Продолжить") + text: qsTr("Continue") + + onClicked: function() { + PageController.goToPage(PageEnum.PageSetupWizardInstalling); + InstallController.install(containers.dockerContainer, + containers.containerDefaultPort, + containers.containerDefaultTransportProto) + } } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index c2c761cc..b4f44fd9 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -109,7 +109,7 @@ Item { running: false onTriggered: { // todo go to root installing page - PageController.goToPage(PageEnum.PageHome) + PageController.goToPage(PageEnum.PageStart) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 9499f5a5..e7917627 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -157,7 +157,6 @@ Item { onClicked: function() { PageController.goToPage(PageEnum.PageSetupWizardInstalling); - InstallController.install(dockerContainer, port.textFieldText, transportProtoButtonGroup.currentIndex) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index 11b09edd..f7cd382f 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -19,9 +19,14 @@ Item { sourceModel: ContainersModel filters: [ ValueFilter { - roleName: "service_type_role" + roleName: "serviceType" value: ProtocolEnum.Vpn + }, + ValueFilter { + roleName: "isSupported" + value: true } + ] } @@ -77,8 +82,8 @@ Item { Layout.topMargin: 16 Layout.bottomMargin: 16 - text: name_role - descriptionText: desc_role + text: name + descriptionText: description buttonImage: "qrc:/images/controls/chevron-right.svg" onClickedFunc: function() { diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index 8293b210..b7d56ce5 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -8,6 +8,7 @@ import "./" import "../Controls2" import "../Config" import "../Controls2/TextTypes" +import "../Components" Item { id: root @@ -55,7 +56,7 @@ Item { text: qsTr("У меня есть данные для подключения") onClicked: { - drawer.visible = true + connectionTypeSelection.visible = true } } @@ -80,82 +81,8 @@ Item { } } - Drawer { - id: drawer - - edge: Qt.BottomEdge - width: parent.width - height: parent.height * 0.4375 - - clip: true - modal: true - - background: Rectangle { - anchors.fill: parent - anchors.bottomMargin: -radius - radius: 16 - color: "#1C1D21" - - border.color: "#2C2D30" - border.width: 1 - } - - Overlay.modal: Rectangle { - color: Qt.rgba(14/255, 14/255, 17/255, 0.8) - } - - ColumnLayout { - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - anchors.rightMargin: 16 - anchors.leftMargin: 16 - - Header2TextType { - Layout.fillWidth: true - Layout.topMargin: 24 - Layout.alignment: Qt.AlignHCenter - - text: "Данные для подключения" - wrapMode: Text.WordWrap - } - - LabelWithButtonType { - id: ip - Layout.fillWidth: true - Layout.topMargin: 32 - - text: "IP, логин и пароль от сервера" - buttonImage: "qrc:/images/controls/chevron-right.svg" - - onClickedFunc: function() { - PageController.goToPage(PageEnum.PageSetupWizardCredentials) - drawer.visible = false - } - } - Rectangle { - Layout.fillWidth: true - height: 1 - color: "#2C2D30" - } - LabelWithButtonType { - Layout.fillWidth: true - - text: "QR-код, ключ или файл настроек" - buttonImage: "qrc:/images/controls/chevron-right.svg" - - onClickedFunc: function() { - PageController.goToPage(PageEnum.PageSetupWizardConfigSource) - drawer.visible = false - } - } - Rectangle { - Layout.fillWidth: true - height: 1 - color: "#2C2D30" - } - } + ConnectionTypeSelectionDrawer { + id: connectionTypeSelection } } } diff --git a/client/ui/uilogic.cpp b/client/ui/uilogic.cpp index 3ad74645..1cb43ee8 100644 --- a/client/ui/uilogic.cpp +++ b/client/ui/uilogic.cpp @@ -350,7 +350,7 @@ void UiLogic::installServer(QPair &container) QJsonObject server; server.insert(config_key::hostName, m_installCredentials.hostName); server.insert(config_key::userName, m_installCredentials.userName); - server.insert(config_key::password, m_installCredentials.password); + server.insert(config_key::password, m_installCredentials.secretData); server.insert(config_key::port, m_installCredentials.port); server.insert(config_key::description, m_settings->nextAvailableServerName()); @@ -574,7 +574,7 @@ ErrorCode UiLogic::addAlreadyInstalledContainersGui(bool &isServerCreated) if (createNewServer) { server.insert(config_key::hostName, installCredentials.hostName); server.insert(config_key::userName, installCredentials.userName); - server.insert(config_key::password, installCredentials.password); + server.insert(config_key::password, installCredentials.secretData); server.insert(config_key::port, installCredentials.port); server.insert(config_key::description, m_settings->nextAvailableServerName()); } From ca6b7fbeb260fd1c61cf959eec7d16c806ba228d Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 22 May 2023 22:11:20 +0800 Subject: [PATCH 021/278] added importController --- client/amnezia_application.cpp | 3 + client/amnezia_application.h | 2 + client/ui/controllers/importController.cpp | 204 ++++++++++++++++++ client/ui/controllers/importController.h | 37 ++++ .../Pages2/PageSetupWizardConfigSource.qml | 12 +- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 1 + 6 files changed, 258 insertions(+), 1 deletion(-) create mode 100644 client/ui/controllers/importController.cpp create mode 100644 client/ui/controllers/importController.h diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index df4a4ec1..9fdddc86 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -124,6 +124,9 @@ void AmneziaApplication::init() m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_settings)); m_engine->rootContext()->setContextProperty("InstallController", m_installController.get()); + m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings)); + m_engine->rootContext()->setContextProperty("ImportController", m_importController.get()); + // m_uiLogic->registerPagesLogic(); diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 97b16f13..f4156d15 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -19,6 +19,7 @@ #include "ui/controllers/connectionController.h" #include "ui/controllers/pageController.h" #include "ui/controllers/installController.h" +#include "ui/controllers/importController.h" #define amnApp (static_cast(QCoreApplication::instance())) @@ -71,6 +72,7 @@ private: QScopedPointer m_connectionController; QScopedPointer m_pageController; QScopedPointer m_installController; + QScopedPointer m_importController; }; diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp new file mode 100644 index 00000000..ea1e7ad6 --- /dev/null +++ b/client/ui/controllers/importController.cpp @@ -0,0 +1,204 @@ +#include "importController.h" + +#include + +namespace { + enum class ConfigTypes { + Amnezia, + OpenVpn, + WireGuard + }; + + ConfigTypes checkConfigFormat(const QString &config) + { + const QString openVpnConfigPatternCli = "client"; + const QString openVpnConfigPatternProto1 = "proto tcp"; + const QString openVpnConfigPatternProto2 = "proto udp"; + const QString openVpnConfigPatternDriver1 = "dev tun"; + const QString openVpnConfigPatternDriver2 = "dev tap"; + + const QString wireguardConfigPatternSectionInterface = "[Interface]"; + const QString wireguardConfigPatternSectionPeer = "[Peer]"; + + if (config.contains(openVpnConfigPatternCli) && + (config.contains(openVpnConfigPatternProto1) || config.contains(openVpnConfigPatternProto2)) && + (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) { + return ConfigTypes::OpenVpn; + } else if (config.contains(wireguardConfigPatternSectionInterface) && config.contains(wireguardConfigPatternSectionPeer)) { + return ConfigTypes::WireGuard; + } + return ConfigTypes::Amnezia; + } +} + +ImportController::ImportController(const QSharedPointer &serversModel, + const QSharedPointer &containersModel, + const std::shared_ptr &settings, + QObject *parent) : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings) +{ + +} + +bool ImportController::importFromFile(const QUrl &fileUrl) +{ + QFile file(fileUrl.toLocalFile()); + if (file.open(QIODevice::ReadOnly)) { + QByteArray data = file.readAll(); + + auto configFormat = checkConfigFormat(data); + if (configFormat == ConfigTypes::OpenVpn) { + return importOpenVpnConfig(data); + } else if (configFormat == ConfigTypes::WireGuard) { + return importWireGuardConfig(data); + } else { + return importAmneziaConfig(data); + } + } + return false; +} + +bool ImportController::import(const QJsonObject &config) +{ + ServerCredentials credentials; + credentials.hostName = config.value(config_key::hostName).toString(); + credentials.port = config.value(config_key::port).toInt(); + credentials.userName = config.value(config_key::userName).toString(); + credentials.secretData = config.value(config_key::password).toString(); + + if (credentials.isValid() || config.contains(config_key::containers)) { + m_settings->addServer(config); + + if (config.value(config_key::containers).toArray().isEmpty()) { + m_settings->setDefaultServer(m_settings->serversCount() - 1); + } + + emit importFinished(); + } else { + qDebug() << "Failed to import profile"; + qDebug().noquote() << QJsonDocument(config).toJson(); + return false; + } + + return true; +} + +bool ImportController::importAmneziaConfig(QString data) +{ + data.replace("vpn://", ""); + QByteArray ba = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + + QByteArray ba_uncompressed = qUncompress(ba); + if (!ba_uncompressed.isEmpty()) { + ba = ba_uncompressed; + } + + QJsonObject config; + config = QJsonDocument::fromJson(ba).object(); + if (!config.isEmpty()) { + return import(config); + } + + return false; +} + +//bool ImportController::importConnectionFromQr(const QByteArray &data) +//{ +// QJsonObject dataObj = QJsonDocument::fromJson(data).object(); +// if (!dataObj.isEmpty()) { +// return importConnection(dataObj); +// } + +// QByteArray ba_uncompressed = qUncompress(data); +// if (!ba_uncompressed.isEmpty()) { +// return importConnection(QJsonDocument::fromJson(ba_uncompressed).object()); +// } + +// return false; +//} + +bool ImportController::importOpenVpnConfig(const QString &data) +{ + QJsonObject openVpnConfig; + openVpnConfig[config_key::config] = data; + + QJsonObject lastConfig; + lastConfig[config_key::last_config] = QString(QJsonDocument(openVpnConfig).toJson()); + lastConfig[config_key::isThirdPartyConfig] = true; + + QJsonObject containers; + containers.insert(config_key::container, QJsonValue("amnezia-openvpn")); + containers.insert(config_key::openvpn, QJsonValue(lastConfig)); + + QJsonArray arr; + arr.push_back(containers); + + QString hostName; + const static QRegularExpression hostNameRegExp("remote (.*) [0-9]*"); + QRegularExpressionMatch hostNameMatch = hostNameRegExp.match(data); + if (hostNameMatch.hasMatch()) { + hostName = hostNameMatch.captured(1); + } + + QJsonObject config; + config[config_key::containers] = arr; + config[config_key::defaultContainer] = "amnezia-openvpn"; + config[config_key::description] = m_settings->nextAvailableServerName(); + + + const static QRegularExpression dnsRegExp("dhcp-option DNS (\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)"); + QRegularExpressionMatchIterator dnsMatch = dnsRegExp.globalMatch(data); + if (dnsMatch.hasNext()) { + config[config_key::dns1] = dnsMatch.next().captured(1); + } + if (dnsMatch.hasNext()) { + config[config_key::dns2] = dnsMatch.next().captured(1); + } + + config[config_key::hostName] = hostName; + + return import(config); +} + +bool ImportController::importWireGuardConfig(const QString &data) +{ + QJsonObject lastConfig; + lastConfig[config_key::config] = data; + + const static QRegularExpression hostNameAndPortRegExp("Endpoint = (.*):([0-9]*)"); + QRegularExpressionMatch hostNameAndPortMatch = hostNameAndPortRegExp.match(data); + QString hostName; + QString port; + if (hostNameAndPortMatch.hasMatch()) { + hostName = hostNameAndPortMatch.captured(1); + port = hostNameAndPortMatch.captured(2); + } + + QJsonObject wireguardConfig; + wireguardConfig[config_key::last_config] = QString(QJsonDocument(lastConfig).toJson()); + wireguardConfig[config_key::isThirdPartyConfig] = true; + wireguardConfig[config_key::port] = port; + wireguardConfig[config_key::transport_proto] = "udp"; + + QJsonObject containers; + containers.insert(config_key::container, QJsonValue("amnezia-wireguard")); + containers.insert(config_key::wireguard, QJsonValue(wireguardConfig)); + + QJsonArray arr; + arr.push_back(containers); + + QJsonObject config; + config[config_key::containers] = arr; + config[config_key::defaultContainer] = "amnezia-wireguard"; + config[config_key::description] = m_settings->nextAvailableServerName(); + + const static QRegularExpression dnsRegExp("DNS = (\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b).*(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)"); + QRegularExpressionMatch dnsMatch = dnsRegExp.match(data); + if (dnsMatch.hasMatch()) { + config[config_key::dns1] = dnsMatch.captured(1); + config[config_key::dns2] = dnsMatch.captured(2); + } + + config[config_key::hostName] = hostName; + + return import(config); +} diff --git a/client/ui/controllers/importController.h b/client/ui/controllers/importController.h new file mode 100644 index 00000000..7dd548d8 --- /dev/null +++ b/client/ui/controllers/importController.h @@ -0,0 +1,37 @@ +#ifndef IMPORTCONTROLLER_H +#define IMPORTCONTROLLER_H + +#include + +#include "core/defs.h" +#include "containers/containers_defs.h" +#include "ui/models/servers_model.h" +#include "ui/models/containers_model.h" + +class ImportController : public QObject +{ + Q_OBJECT +public: + explicit ImportController(const QSharedPointer &serversModel, + const QSharedPointer &containersModel, + const std::shared_ptr &settings, + QObject *parent = nullptr); + +public slots: + bool importFromFile(const QUrl &fileUrl); + +signals: + void importFinished(); +private: + bool import(const QJsonObject &config); + bool importAmneziaConfig(QString data); + bool importOpenVpnConfig(const QString &data); + bool importWireGuardConfig(const QString &data); + + QSharedPointer m_serversModel; + QSharedPointer m_containersModel; + std::shared_ptr m_settings; + +}; + +#endif // IMPORTCONTROLLER_H diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index db2730ac..5f2bf2f3 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -13,6 +13,14 @@ import "../Config" Item { id: root + Connections { + target: ImportController + + function onImportFinished() { + + } + } + FlickableType { id: fl anchors.top: root.top @@ -63,7 +71,7 @@ Item { FileDialog { id: fileDialog onAccepted: { - + ImportController.importFromFile(selectedFile) } } } @@ -84,6 +92,7 @@ Item { onClickedFunc: function() { } } + Rectangle { Layout.fillWidth: true height: 1 @@ -101,6 +110,7 @@ Item { PageController.goToPage(PageEnum.PageSetupWizardTextKey) } } + Rectangle { Layout.fillWidth: true height: 1 diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index 9f53a240..9555c915 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -124,6 +124,7 @@ Item { text: qsTr("Continue") onClicked: function() { + ContainersModel.setCurrentlyInstalledContainerIndex(containers.dockerContainer) PageController.goToPage(PageEnum.PageSetupWizardInstalling); InstallController.install(containers.dockerContainer, containers.containerDefaultPort, From e00656d757646e5fa9c7339fde611969e04d8deb Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 25 May 2023 15:40:17 +0800 Subject: [PATCH 022/278] added PageSettings and PageSettingsServersList. - replaced PageLoader with PageType with stackView property. - added error handling when installing a server/container --- client/images/controls/settings.svg | 4 + client/resources.qrc | 8 +- client/ui/controllers/installController.cpp | 22 ++-- client/ui/controllers/installController.h | 9 +- client/ui/controllers/pageController.cpp | 8 +- client/ui/controllers/pageController.h | 6 +- client/ui/models/servers_model.cpp | 10 +- client/ui/models/servers_model.h | 6 +- client/ui/pages.h | 21 ++-- .../ConnectionTypeSelectionDrawer.qml | 8 +- .../Components/ContainersPageHomeListView.qml | 2 +- client/ui/qml/Controls2/Header2Type.qml | 2 +- client/ui/qml/Controls2/HeaderType.qml | 6 +- .../ui/qml/Controls2/LabelWithButtonType.qml | 6 +- client/ui/qml/Controls2/PageType.qml | 31 +++++ client/ui/qml/Controls2/PopupType.qml | 61 ++++++++++ .../Controls2/TextTypes/CaptionTextType.qml | 13 ++ client/ui/qml/PageLoader.qml | 37 ------ client/ui/qml/Pages2/PageHome.qml | 35 ++++-- client/ui/qml/Pages2/PageSettings.qml | 68 ++++++++++- .../ui/qml/Pages2/PageSettingsServersList.qml | 111 ++++++++++++++++++ .../Pages2/PageSetupWizardConfigSource.qml | 12 +- .../qml/Pages2/PageSetupWizardCredentials.qml | 6 +- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 11 +- .../qml/Pages2/PageSetupWizardInstalling.qml | 32 ++--- .../PageSetupWizardProtocolSettings.qml | 4 +- .../qml/Pages2/PageSetupWizardProtocols.qml | 6 +- client/ui/qml/Pages2/PageSetupWizardStart.qml | 36 +++++- .../ui/qml/Pages2/PageSetupWizardTextKey.qml | 4 +- client/ui/qml/Pages2/PageStart.qml | 39 +++++- client/ui/qml/main2.qml | 4 +- 31 files changed, 486 insertions(+), 142 deletions(-) create mode 100644 client/images/controls/settings.svg create mode 100644 client/ui/qml/Controls2/PageType.qml create mode 100644 client/ui/qml/Controls2/PopupType.qml create mode 100644 client/ui/qml/Controls2/TextTypes/CaptionTextType.qml delete mode 100644 client/ui/qml/PageLoader.qml create mode 100644 client/ui/qml/Pages2/PageSettingsServersList.qml diff --git a/client/images/controls/settings.svg b/client/images/controls/settings.svg new file mode 100644 index 00000000..0693fb4e --- /dev/null +++ b/client/images/controls/settings.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/resources.qrc b/client/resources.qrc index ff7139a8..ca1674af 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -184,7 +184,6 @@ ui/qml/Controls2/DropDownType.qml ui/qml/Pages2/PageSetupWizardStart.qml ui/qml/main2.qml - ui/qml/PageLoader.qml images/amneziaBigLogo.png images/amneziaBigLogo.svg ui/qml/Controls2/FlickableType.qml @@ -215,7 +214,7 @@ images/controls/settings-2.svg images/controls/share-2.svg ui/qml/Pages2/PageHome.qml - ui/qml/Pages2/PageSettings.qml + ui/qml/Pages2/PageSettingsServersList.qml ui/qml/Pages2/PageShare.qml ui/qml/Controls2/TextTypes/Header1TextType.qml ui/qml/Controls2/TextTypes/LabelTextType.qml @@ -230,5 +229,10 @@ ui/qml/Controls2/ProgressBarType.qml ui/qml/Components/ConnectionTypeSelectionDrawer.qml ui/qml/Components/ContainersPageHomeListView.qml + ui/qml/Controls2/TextTypes/CaptionTextType.qml + images/controls/settings.svg + ui/qml/Pages2/PageSettings.qml + ui/qml/Controls2/PageType.qml + ui/qml/Controls2/PopupType.qml diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 3e4809e6..31f58a2a 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -3,6 +3,7 @@ #include #include "core/servercontroller.h" +#include "core/errorstrings.h" InstallController::InstallController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, @@ -12,7 +13,7 @@ InstallController::InstallController(const QSharedPointer &servers } -ErrorCode InstallController::install(DockerContainer container, int port, TransportProto transportProto) +void InstallController::install(DockerContainer container, int port, TransportProto transportProto) { Proto mainProto = ContainerProps::defaultProtocol(container); @@ -26,13 +27,13 @@ ErrorCode InstallController::install(DockerContainer container, int port, Transp }; if (m_shouldCreateServer) { - return installServer(container, config); + installServer(container, config); } else { - return installContainer(container, config); + installContainer(container, config); } } -ErrorCode InstallController::installServer(DockerContainer container, QJsonObject& config) +void InstallController::installServer(DockerContainer container, QJsonObject& config) { //todo check if container already installed ServerController serverController(m_settings); @@ -51,15 +52,14 @@ ErrorCode InstallController::installServer(DockerContainer container, QJsonObjec m_settings->addServer(server); m_settings->setDefaultServer(m_settings->serversCount() - 1); - //todo change to server finished - emit installContainerFinished(); + emit installServerFinished(); + return; } - //todo error processing - return errorCode; + emit installationErrorOccurred(errorString(errorCode)); } -ErrorCode InstallController::installContainer(DockerContainer container, QJsonObject& config) +void InstallController::installContainer(DockerContainer container, QJsonObject& config) { //todo check if container already installed ServerCredentials serverCredentials = m_serversModel->getCurrentlyProcessedServerCredentials(); @@ -69,10 +69,10 @@ ErrorCode InstallController::installContainer(DockerContainer container, QJsonOb if (errorCode == ErrorCode::NoError) { m_containersModel->setData(m_containersModel->index(container), config, ContainersModel::Roles::ConfigRole); emit installContainerFinished(); + return; } - //todo error processing - return errorCode; + emit installationErrorOccurred(errorString(errorCode)); } void InstallController::setCurrentlyInstalledServerCredentials(const QString &hostName, const QString &userName, const QString &secretData) diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index c0d6ad20..d9e1ad95 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -18,15 +18,18 @@ public: QObject *parent = nullptr); public slots: - ErrorCode install(DockerContainer container, int port, TransportProto transportProto); + void install(DockerContainer container, int port, TransportProto transportProto); void setCurrentlyInstalledServerCredentials(const QString &hostName, const QString &userName, const QString &secretData); void setShouldCreateServer(bool shouldCreateServer); signals: void installContainerFinished(); + void installServerFinished(); + + void installationErrorOccurred(QString errorMessage); private: - ErrorCode installServer(DockerContainer container, QJsonObject& config); - ErrorCode installContainer(DockerContainer container, QJsonObject& config); + void installServer(DockerContainer container, QJsonObject& config); + void installContainer(DockerContainer container, QJsonObject& config); QSharedPointer m_serversModel; QSharedPointer m_containersModel; diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/pageController.cpp index 10120767..63c3ba58 100644 --- a/client/ui/controllers/pageController.cpp +++ b/client/ui/controllers/pageController.cpp @@ -5,15 +5,15 @@ PageController::PageController(const QSharedPointer &serversModel, { } -void PageController::setStartPage() +QString PageController::getInitialPage() { if (m_serversModel->getServersCount()) { if (m_serversModel->getDefaultServerIndex() < 0) { m_serversModel->setDefaultServerIndex(0); } - emit goToPage(PageLoader::PageEnum::PageStart, false); + return getPagePath(PageLoader::PageEnum::PageStart); } else { - emit goToPage(PageLoader::PageEnum::PageSetupWizardStart, false); + return getPagePath(PageLoader::PageEnum::PageSetupWizardStart); } } @@ -21,5 +21,5 @@ QString PageController::getPagePath(PageLoader::PageEnum page) { QMetaEnum metaEnum = QMetaEnum::fromType(); QString pageName = metaEnum.valueToKey(static_cast(page)); - return "Pages2/" + pageName + ".qml"; + return "qrc:/ui/qml/Pages2/" + pageName + ".qml"; } diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 9bfd6bda..f37edca9 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -36,12 +36,12 @@ public: QObject *parent = nullptr); public slots: - void setStartPage(); + QString getInitialPage(); QString getPagePath(PageLoader::PageEnum page); signals: - void goToPage(PageLoader::PageEnum page, bool slide = true); - void closePage(); + void goToPageHome(); + void showErrorMessage(QString errorMessage); private: QSharedPointer m_serversModel; diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index fe973811..884009e8 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -51,6 +51,8 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const return QVariant::fromValue(m_settings->serverCredentials(index.row())); case IsDefaultRole: return index.row() == m_settings->defaultServerIndex(); + case IsCurrentlyProcessedRole: + return index.row() == m_currenlyProcessedServerIndex; } return QVariant(); @@ -59,7 +61,7 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const //todo mode to setData? void ServersModel::setDefaultServerIndex(int index) { -// beginResetModel(); + // beginResetModel(); m_settings->setDefaultServer(index); // endResetModel(); } @@ -84,11 +86,17 @@ ServerCredentials ServersModel::getCurrentlyProcessedServerCredentials() return qvariant_cast(data(index(m_currenlyProcessedServerIndex), CredentialsRole)); } +void ServersModel::addServer() +{ + +} + QHash ServersModel::roleNames() const { QHash roles; roles[NameRole] = "name"; roles[HostNameRole] = "hostName"; roles[CredentialsRole] = "credentials"; roles[IsDefaultRole] = "isDefault"; + roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed"; return roles; } diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index 08b44976..ae1ec8d9 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -19,7 +19,8 @@ public: NameRole = Qt::UserRole + 1, HostNameRole, CredentialsRole, - IsDefaultRole + IsDefaultRole, + IsCurrentlyProcessedRole }; ServersModel(std::shared_ptr settings, QObject *parent = nullptr); @@ -32,11 +33,14 @@ public: public slots: void setDefaultServerIndex(int index); const int getDefaultServerIndex(); + const int getServersCount(); void setCurrentlyProcessedServerIndex(int index); ServerCredentials getCurrentlyProcessedServerCredentials(); + void addServer(); + protected: QHash roleNames() const override; diff --git a/client/ui/pages.h b/client/ui/pages.h index b2e191c7..82c0d409 100644 --- a/client/ui/pages.h +++ b/client/ui/pages.h @@ -21,18 +21,19 @@ public: namespace PageEnumNS { Q_NAMESPACE -enum class Page {Start = 0, NewServer, NewServerProtocols, Vpn, - Wizard, WizardLow, WizardMedium, WizardHigh, WizardVpnMode, ServerConfiguringProgress, - GeneralSettings, AppSettings, NetworkSettings, ServerSettings, - ServerContainers, ServersList, ShareConnection, Sites, - ProtocolSettings, ProtocolShare, QrDecoder, QrDecoderIos, About, ViewConfig, - AdvancedServerSettings, ClientManagement, ClientInfo, +enum class Page { Start = 0, NewServer, NewServerProtocols, Vpn, + Wizard, WizardLow, WizardMedium, WizardHigh, WizardVpnMode, ServerConfiguringProgress, + GeneralSettings, AppSettings, NetworkSettings, ServerSettings, + ServerContainers, ServersList, ShareConnection, Sites, + ProtocolSettings, ProtocolShare, QrDecoder, QrDecoderIos, About, ViewConfig, + AdvancedServerSettings, ClientManagement, ClientInfo, - PageSetupWizardStart, PageTest, PageSetupWizardCredentials, PageSetupWizardProtocols, PageSetupWizardEasy, - PageSetupWizardProtocolSettings, PageSetupWizardInstalling, PageSetupWizardConfigSource, - PageSetupWizardTextKey, + PageSetupWizardStart, PageTest, PageSetupWizardCredentials, PageSetupWizardProtocols, PageSetupWizardEasy, + PageSetupWizardProtocolSettings, PageSetupWizardInstalling, PageSetupWizardConfigSource, PageSetupWizardTextKey, - PageStart, PageHome, PageSettings, PageShare}; + PageSettings, PageSettingsServersList, + + PageStart, PageHome, PageShare}; Q_ENUM_NS(Page) static void declareQmlPageEnum() { diff --git a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml index 3ae5b278..20029eb0 100644 --- a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml +++ b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml @@ -57,8 +57,8 @@ Drawer { text: "IP, логин и пароль от сервера" buttonImage: "qrc:/images/controls/chevron-right.svg" - onClickedFunc: function() { - PageController.goToPage(PageEnum.PageSetupWizardCredentials) + clickedFunction: function() { + goToPage(PageEnum.PageSetupWizardCredentials) root.visible = false } } @@ -73,8 +73,8 @@ Drawer { text: "QR-код, ключ или файл настроек" buttonImage: "qrc:/images/controls/chevron-right.svg" - onClickedFunc: function() { - PageController.goToPage(PageEnum.PageSetupWizardConfigSource) + clickedFunction: function() { + goToPage(PageEnum.PageSetupWizardConfigSource) root.visible = false } } diff --git a/client/ui/qml/Components/ContainersPageHomeListView.qml b/client/ui/qml/Components/ContainersPageHomeListView.qml index b3d34b62..309b4217 100644 --- a/client/ui/qml/Components/ContainersPageHomeListView.qml +++ b/client/ui/qml/Components/ContainersPageHomeListView.qml @@ -96,7 +96,7 @@ ListView { } else { ContainersModel.setCurrentlyInstalledContainerIndex(proxyContainersModel.mapToSource(index)) InstallController.setShouldCreateServer(false) - PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) + goToPage(PageEnum.PageSetupWizardProtocolSettings) containersDropDown.menuVisible = false menu.visible = false } diff --git a/client/ui/qml/Controls2/Header2Type.qml b/client/ui/qml/Controls2/Header2Type.qml index f985d523..ce9d804a 100644 --- a/client/ui/qml/Controls2/Header2Type.qml +++ b/client/ui/qml/Controls2/Header2Type.qml @@ -36,7 +36,7 @@ Item { if (backButtonFunction && typeof backButtonFunction === "function") { backButtonFunction() } else { - PageController.closePage() + closePage() } } } diff --git a/client/ui/qml/Controls2/HeaderType.qml b/client/ui/qml/Controls2/HeaderType.qml index 6c4e7847..2c0fbd85 100644 --- a/client/ui/qml/Controls2/HeaderType.qml +++ b/client/ui/qml/Controls2/HeaderType.qml @@ -36,7 +36,7 @@ Item { if (backButtonFunction && typeof backButtonFunction === "function") { backButtonFunction() } else { - PageController.closePage() + closePage() } } } @@ -61,8 +61,8 @@ Item { visible: image ? true : false onClicked: { - if (actionButtonImage && typeof actionButtonImage === "function") { - actionButtonImage() + if (actionButtonFunction && typeof actionButtonFunction === "function") { + actionButtonFunction() } } } diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml index d8e195f1..2530f583 100644 --- a/client/ui/qml/Controls2/LabelWithButtonType.qml +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -8,7 +8,7 @@ Item { property string text property string descriptionText - property var onClickedFunc + property var clickedFunction property alias buttonImage: button.image property string iconImage @@ -68,8 +68,8 @@ Item { hoverEnabled: false image: buttonImage onClicked: { - if (onClickedFunc && typeof onClickedFunc === "function") { - onClickedFunc() + if (clickedFunction && typeof clickedFunction === "function") { + clickedFunction() } } diff --git a/client/ui/qml/Controls2/PageType.qml b/client/ui/qml/Controls2/PageType.qml new file mode 100644 index 00000000..4eb51753 --- /dev/null +++ b/client/ui/qml/Controls2/PageType.qml @@ -0,0 +1,31 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Item { + id: root + + property StackView stackView: StackView.view + + function goToPage(page, slide = true) { + if (slide) { + root.stackView.push(PageController.getPagePath(page), {}, StackView.PushTransition) + } else { + root.stackView.push(PageController.getPagePath(page), {}, StackView.Immediate) + } + } + + function closePage() { + if (root.stackView.depth <= 1) { + return + } + + root.stackView.pop() + } + + function goToStartPage() { + while (root.stackView.depth > 1) { + root.stackView.pop() + } + } +} diff --git a/client/ui/qml/Controls2/PopupType.qml b/client/ui/qml/Controls2/PopupType.qml new file mode 100644 index 00000000..dd92d5fe --- /dev/null +++ b/client/ui/qml/Controls2/PopupType.qml @@ -0,0 +1,61 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import "TextTypes" + +Popup { + id: root + + property string popupErrorMessageText + property bool closeButtonVisible: true + + leftMargin: 25 + rightMargin: 25 + bottomMargin: 70 + + width: parent.width - leftMargin - rightMargin + + anchors.centerIn: parent + modal: true + closePolicy: Popup.CloseOnEscape + + Overlay.modal: Rectangle { + color: Qt.rgba(14/255, 14/255, 17/255, 0.8) + } + + background: Rectangle { + anchors.fill: parent + + color: Qt.rgba(215/255, 216/255, 219/255, 0.95) + radius: 4 + } + + contentItem: RowLayout { + width: parent.width + + CaptionTextType { + horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + + text: root.popupErrorMessageText + } + + BasicButtonType { + visible: closeButtonVisible + + defaultColor: Qt.rgba(215/255, 216/255, 219/255, 0.95) + hoveredColor: "#C1C2C5" + pressedColor: "#AEB0B7" + disabledColor: "#494B50" + + textColor: "#0E0E11" + borderWidth: 0 + + text: "Close" + onClicked: { + root.close() + } + } + } +} diff --git a/client/ui/qml/Controls2/TextTypes/CaptionTextType.qml b/client/ui/qml/Controls2/TextTypes/CaptionTextType.qml new file mode 100644 index 00000000..15cc96c1 --- /dev/null +++ b/client/ui/qml/Controls2/TextTypes/CaptionTextType.qml @@ -0,0 +1,13 @@ +import QtQuick + +Text { + height: 16 + + color: "#0E0E11" + font.pixelSize: 13 + font.weight: Font.Normal + font.family: "PT Root UI VF" + font.letterSpacing: 0.02 + + wrapMode: Text.WordWrap +} diff --git a/client/ui/qml/PageLoader.qml b/client/ui/qml/PageLoader.qml deleted file mode 100644 index b3d53a05..00000000 --- a/client/ui/qml/PageLoader.qml +++ /dev/null @@ -1,37 +0,0 @@ -import QtQuick -import QtQuick.Controls - -StackView { - id: stackView - - function gotoPage(page, slide) { - if (slide) { - stackView.push(PageController.getPagePath(page), {}, StackView.PushTransition) - } else { - stackView.push(PageController.getPagePath(page), {}, StackView.Immediate) - } - } - - function closePage() { - if (stackView.depth <= 1) { - return - } - - stackView.pop() - } - - Connections { - target: PageController - function onGoToPage(page, slide) { - stackView.gotoPage(page, slide) - } - - function onClosePage() { - stackView.closePage() - } - } - - Component.onCompleted: { - PageController.setStartPage() - } -} diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index a04b1744..d5455849 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -14,7 +14,7 @@ import "../Controls2/TextTypes" import "../Config" import "../Components" -Item { +PageType { id: root property string defaultColor: "#1C1D21" @@ -37,6 +37,21 @@ Item { } } + Connections { + target: InstallController + + function onInstallContainerFinished() { + goToStartPage() + menu.visible = true + containersDropDown.menuVisible = true + } + + function onInstallServerFinished() { + goToStartPage() + menu.visible = true + } + } + Rectangle { id: buttonBackground anchors.fill: buttonContent @@ -279,6 +294,15 @@ Item { z: 1 + Image { + source: "qrc:/images/controls/check.svg" + visible: serverRadioButton.checked + width: 24 + height: 24 + + Layout.rightMargin: 8 + } + Text { id: serverRadioButtonText @@ -295,13 +319,10 @@ Item { Layout.bottomMargin: 20 } - Image { - source: "qrc:/images/controls/check.svg" - visible: serverRadioButton.checked - width: 24 - height: 24 + ImageButtonType { + image: "qrc:/images/controls/settings.svg" - Layout.rightMargin: 8 +// onClicked: } } diff --git a/client/ui/qml/Pages2/PageSettings.qml b/client/ui/qml/Pages2/PageSettings.qml index 5560aee7..9d5b7442 100644 --- a/client/ui/qml/Pages2/PageSettings.qml +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -1,5 +1,71 @@ import QtQuick +import QtQuick.Controls +import QtQuick.Layouts -Item { +import SortFilterProxyModel 0.2 +import PageEnum 1.0 +import ContainerProps 1.0 +import ProtocolProps 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +PageType { + id: root + + SortFilterProxyModel { + id: proxyServersModel + sourceModel: ServersModel + filters: [ + ValueFilter { + roleName: "isCurrentlyProcessed" + value: true + } + ] + } + + FlickableType { + id: fl + anchors.fill: parent + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + spacing: 16 + + Repeater { + model: proxyServersModel + + delegate: HeaderType { + id: header + + Layout.fillWidth: true + Layout.topMargin: 20 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + actionButtonImage: "qrc:/images/controls/plus.svg" + backButtonImage: "qrc:/images/controls/arrow-left.svg" + + headerText: name + + actionButtonFunction: function() { + connectionTypeSelection.visible = true + } + + backButtonFunction: function() { + closePage() + } + } + } + } + } } diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml new file mode 100644 index 00000000..79e24f75 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -0,0 +1,111 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 +import ContainerProps 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + HeaderType { + id: header + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + actionButtonImage: "qrc:/images/controls/plus.svg" + backButtonImage: "qrc:/images/controls/arrow-left.svg" + + headerText: "Серверы" + + actionButtonFunction: function() { + connectionTypeSelection.visible = true + } + + backButtonFunction: function() { + PageController.goToPageHome() + } + } + + ConnectionTypeSelectionDrawer { + id: connectionTypeSelection + } + + 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 + + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + spacing: 16 + + ListView { + id: servers + width: parent.width + height: servers.contentItem.height + + model: ServersModel + + clip: true + + delegate: Item { + implicitWidth: servers.width + implicitHeight: delegateContent.implicitHeight + + ColumnLayout { + id: delegateContent + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + LabelWithButtonType { + id: server + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.bottomMargin: 16 + + text: name + descriptionText: hostName + buttonImage: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + ServersModel.setCurrentlyProcessedServerIndex(index) + goToPage(PageEnum.PageSettings) + } + } + + Rectangle { + Layout.fillWidth: true + height: 1 + color: "#2C2D30" + } + } + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 5f2bf2f3..53a40889 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -10,7 +10,7 @@ import "../Controls2" import "../Controls2/TextTypes" import "../Config" -Item { +PageType { id: root Connections { @@ -44,7 +44,7 @@ Item { backButtonImage: "qrc:/images/controls/arrow-left.svg" - headerText: "Подключение к серверу" + headerText: "Подключение к серверу" descriptionText: "Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватывать ваши данные.\n Всё в порядке, если код передал друг." } @@ -64,7 +64,7 @@ Item { buttonImage: "qrc:/images/controls/chevron-right.svg" iconImage: "qrc:/images/controls/folder-open.svg" - onClickedFunc: function() { + clickedFunction: function() { onClicked: fileDialog.open() } @@ -89,7 +89,7 @@ Item { buttonImage: "qrc:/images/controls/chevron-right.svg" iconImage: "qrc:/images/controls/qr-code.svg" - onClickedFunc: function() { + clickedFunction: function() { } } @@ -106,8 +106,8 @@ Item { buttonImage: "qrc:/images/controls/chevron-right.svg" iconImage: "qrc:/images/controls/text-cursor.svg" - onClickedFunc: function() { - PageController.goToPage(PageEnum.PageSetupWizardTextKey) + clickedFunction: function() { + goToPage(PageEnum.PageSetupWizardTextKey) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index 20c6e735..95463c3e 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -8,7 +8,7 @@ import "./" import "../Controls2" import "../Config" -Item { +PageType { id: root FlickableType { @@ -69,7 +69,7 @@ Item { InstallController.setShouldCreateServer(true) InstallController.setCurrentlyInstalledServerCredentials(hostname.textField.text, username.textField.text, secretData.textField.text) - PageController.goToPage(PageEnum.PageSetupWizardEasy) + goToPage(PageEnum.PageSetupWizardEasy) } } @@ -90,7 +90,7 @@ Item { InstallController.setShouldCreateServer(true) InstallController.setCurrentlyInstalledServerCredentials(hostname.textField.text, username.textField.text, secretData.textField.text) - PageController.goToPage(PageEnum.PageSetupWizardProtocols) + goToPage(PageEnum.PageSetupWizardProtocols) } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index 9555c915..8707c19b 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -12,7 +12,7 @@ import "./" import "../Controls2" import "../Config" -Item { +PageType { id: root SortFilterProxyModel { @@ -34,7 +34,7 @@ Item { id: fl anchors.top: root.top anchors.bottom: root.bottom - contentHeight: content.height + contentHeight: content.implicitHeight + buttonContinue.anchors.bottomMargin Column { id: content @@ -49,8 +49,9 @@ Item { spacing: 16 HeaderType { + id: header + implicitWidth: parent.width - anchors.topMargin: 20 backButtonImage: "qrc:/images/controls/arrow-left.svg" @@ -117,6 +118,8 @@ Item { } BasicButtonType { + id: buttonContinue + implicitWidth: parent.width anchors.topMargin: 24 anchors.bottomMargin: 32 @@ -125,7 +128,7 @@ Item { onClicked: function() { ContainersModel.setCurrentlyInstalledContainerIndex(containers.dockerContainer) - PageController.goToPage(PageEnum.PageSetupWizardInstalling); + goToPage(PageEnum.PageSetupWizardInstalling); InstallController.install(containers.dockerContainer, containers.containerDefaultPort, containers.containerDefaultTransportProto) diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index b4f44fd9..fd8aac6b 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -11,11 +11,20 @@ import "../Controls2" import "../Controls2/TextTypes" import "../Config" -Item { +PageType { id: root property real progressBarValue: 0 + Connections { + target: InstallController + + function onInstallationErrorOccurred(errorMessage) { + closePage() + PageController.showErrorMessage(errorMessage) + } + } + SortFilterProxyModel { id: proxyContainersModel sourceModel: ContainersModel @@ -100,26 +109,5 @@ Item { } } } - - Timer { - id: closePageTimer - - interval: 1000 - repeat: false - running: false - onTriggered: { - // todo go to root installing page - PageController.goToPage(PageEnum.PageStart) - } - } - - Connections { - target: InstallController - - function onInstallContainerFinished() { - progressBarValue = 1 - closePageTimer.start() - } - } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index e7917627..fa3ac838 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -13,7 +13,7 @@ import "../Controls2" import "../Controls2/TextTypes" import "../Config" -Item { +PageType { id: root SortFilterProxyModel { @@ -156,7 +156,7 @@ Item { text: qsTr("Установить") onClicked: function() { - PageController.goToPage(PageEnum.PageSetupWizardInstalling); + goToPage(PageEnum.PageSetupWizardInstalling); InstallController.install(dockerContainer, port.textFieldText, transportProtoButtonGroup.currentIndex) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index f7cd382f..bffe09cb 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -11,7 +11,7 @@ import "./" import "../Controls2" import "../Config" -Item { +PageType { id: root SortFilterProxyModel { @@ -86,9 +86,9 @@ Item { descriptionText: description buttonImage: "qrc:/images/controls/chevron-right.svg" - onClickedFunc: function() { + clickedFunction: function() { ContainersModel.setCurrentlyInstalledContainerIndex(proxyContainersModel.mapToSource(index)) - PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) + goToPage(PageEnum.PageSetupWizardProtocolSettings) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index b7d56ce5..4d0f4c5c 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -10,9 +10,27 @@ import "../Config" import "../Controls2/TextTypes" import "../Components" -Item { +PageType { id: root + Connections { + target: PageController + + function onShowErrorMessage(errorMessage) { + popupErrorMessage.popupErrorMessageText = errorMessage + popupErrorMessage.open() + } + } + + Connections { + target: InstallController + + function onInstallServerFinished() { + goToStartPage() +// goToPage(PageEnum.PageStart) + } + } + FlickableType { id: fl anchors.top: root.top @@ -44,7 +62,7 @@ Item { Layout.leftMargin: 16 Layout.rightMargin: 16 - text: "Бесплатный сервис для создания личного VPN на вашем сервере. Помогаем получать доступ к заблокированному контенту, не раскрывая конфиденциальность даже провайдерам VPN." + text: "Бесплатный сервис для создания личного VPN на вашем сервере. Помогаем получать доступ к заблокированному контенту, не раскрывая конфиденциальность даже провайдерам VPN." } BasicButtonType { @@ -76,7 +94,7 @@ Item { text: qsTr("У меня ничего нет") onClicked: { - PageController.goToPage(PageEnum.PageTest) + goToPage(PageEnum.PageTest) } } } @@ -85,4 +103,16 @@ Item { id: connectionTypeSelection } } + + Item { + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + + implicitHeight: popupErrorMessage.height + + PopupType { + id: popupErrorMessage + } + } } diff --git a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml index f94b9ff4..43dbda0e 100644 --- a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml +++ b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml @@ -9,7 +9,7 @@ import "../Controls2" import "../Controls2/TextTypes" import "../Config" -Item { +PageType { id: root FlickableType { @@ -66,7 +66,7 @@ Item { text: qsTr("Подключиться") onClicked: function() { -// PageController.goToPage(PageEnum.PageSetupWizardInstalling) +// goToPage(PageEnum.PageSetupWizardInstalling) } } } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index ade0980d..19ea3bc4 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -9,9 +9,22 @@ import "../Controls2" import "../Controls2/TextTypes" import "../Config" -Item { +PageType { id: root + Connections { + target: PageController + + function onGoToPageHome() { + tabBar.currentIndex = 0 + } + + function onShowErrorMessage(errorMessage) { + popupErrorMessage.popupErrorMessageText = errorMessage + popupErrorMessage.open() + } + } + StackLayout { id: stackLayout currentIndex: tabBar.currentIndex @@ -24,10 +37,18 @@ Item { width: parent.width height: root.height - tabBar.implicitHeight - PageHome { + StackView { + id: homeStackView + initialItem: "PageHome.qml" //PageController.getPagePath(PageEnum.PageSettingsServersList) } - PageSetupWizardEasy { + Item { + + } + + StackView { + id: settingsStackView + initialItem: "PageSettingsServersList.qml" //PageController.getPagePath(PageEnum.PageSettingsServersList) } } @@ -69,4 +90,16 @@ Item { cursorShape: Qt.PointingHandCursor enabled: false } + + Item { + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + + implicitHeight: popupErrorMessage.height + + PopupType { + id: popupErrorMessage + } + } } diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index b10d749f..5ca1b345 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -27,9 +27,9 @@ Window { color: "#0E0E11" } - PageLoader { - id: pageLoader + StackView { anchors.fill: parent focus: true + initialItem: PageController.getInitialPage() } } From 1e180489a40b89265c7789edbfb3813985d201e0 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sat, 27 May 2023 22:46:41 +0800 Subject: [PATCH 023/278] added display of vpn containers and services on the settings page - added PageSettingsData and implementation of 'remove all containers' button --- client/images/controls/edit-3.svg | 4 + client/resources.qrc | 5 + client/ui/models/containers_model.cpp | 18 +++ client/ui/models/containers_model.h | 2 + .../ConnectionTypeSelectionDrawer.qml | 22 ++-- .../Components/ContainersPageHomeListView.qml | 1 - .../PageSettingsContainersListView.qml | 107 ++++++++++++++++++ client/ui/qml/Controls2/DividerType.qml | 8 ++ .../ui/qml/Controls2/LabelWithButtonType.qml | 58 +++++++--- .../Controls2/TextTypes/ListItemTitleType.qml | 12 ++ client/ui/qml/Pages2/PageHome.qml | 19 +--- client/ui/qml/Pages2/PageSettings.qml | 95 ++++++++++++++-- client/ui/qml/Pages2/PageSettingsData.qml | 67 +++++++++++ .../ui/qml/Pages2/PageSettingsServersList.qml | 13 +-- .../Pages2/PageSetupWizardConfigSource.qml | 27 ++--- .../qml/Pages2/PageSetupWizardInstalling.qml | 8 ++ .../qml/Pages2/PageSetupWizardProtocols.qml | 10 +- client/ui/qml/Pages2/PageSetupWizardStart.qml | 3 +- client/ui/qml/Pages2/PageStart.qml | 16 ++- 19 files changed, 393 insertions(+), 102 deletions(-) create mode 100644 client/images/controls/edit-3.svg create mode 100644 client/ui/qml/Components/PageSettingsContainersListView.qml create mode 100644 client/ui/qml/Controls2/DividerType.qml create mode 100644 client/ui/qml/Controls2/TextTypes/ListItemTitleType.qml create mode 100644 client/ui/qml/Pages2/PageSettingsData.qml diff --git a/client/images/controls/edit-3.svg b/client/images/controls/edit-3.svg new file mode 100644 index 00000000..4e1dc071 --- /dev/null +++ b/client/images/controls/edit-3.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/resources.qrc b/client/resources.qrc index ca1674af..ca02df7d 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -234,5 +234,10 @@ ui/qml/Pages2/PageSettings.qml ui/qml/Controls2/PageType.qml ui/qml/Controls2/PopupType.qml + images/controls/edit-3.svg + ui/qml/Pages2/PageSettingsData.qml + ui/qml/Components/PageSettingsContainersListView.qml + ui/qml/Controls2/TextTypes/ListItemTitleType.qml + ui/qml/Controls2/DividerType.qml diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index 6a999011..4dc7010e 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -1,5 +1,7 @@ #include "containers_model.h" +#include "core/servercontroller.h" + ContainersModel::ContainersModel(std::shared_ptr settings, QObject *parent) : m_settings(settings), QAbstractListModel(parent) { } @@ -106,6 +108,22 @@ int ContainersModel::getCurrentlyInstalledContainerIndex() return m_currentlyInstalledContainerIndex; } +void ContainersModel::removeAllContainers() +{ + + ServerController serverController(m_settings); + auto errorCode = serverController.removeAllContainers(m_settings->serverCredentials(m_currentlyProcessedServerIndex)); + + if (errorCode == ErrorCode::NoError) { + beginResetModel(); + m_settings->setContainers(m_currentlyProcessedServerIndex, {}); + m_settings->setDefaultContainer(m_currentlyProcessedServerIndex, DockerContainer::None); + endResetModel(); + } + + //todo process errors +} + QHash ContainersModel::roleNames() const { QHash roles; roles[NameRole] = "name"; diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index 36b5567a..642b6680 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -48,6 +48,8 @@ public slots: void setCurrentlyInstalledContainerIndex(int index); int getCurrentlyInstalledContainerIndex(); + void removeAllContainers(); + protected: QHash roleNames() const override; diff --git a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml index 20029eb0..2e6b3f2e 100644 --- a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml +++ b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml @@ -37,12 +37,11 @@ Drawer { anchors.left: parent.left anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 - Header2TextType { Layout.fillWidth: true Layout.topMargin: 24 + Layout.rightMargin: 16 + Layout.leftMargin: 16 Layout.alignment: Qt.AlignHCenter text: "Данные для подключения" @@ -52,7 +51,7 @@ Drawer { LabelWithButtonType { id: ip Layout.fillWidth: true - Layout.topMargin: 32 + Layout.topMargin: 16 text: "IP, логин и пароль от сервера" buttonImage: "qrc:/images/controls/chevron-right.svg" @@ -62,11 +61,9 @@ Drawer { root.visible = false } } - Rectangle { - Layout.fillWidth: true - height: 1 - color: "#2C2D30" - } + + DividerType {} + LabelWithButtonType { Layout.fillWidth: true @@ -78,10 +75,7 @@ Drawer { root.visible = false } } - Rectangle { - Layout.fillWidth: true - height: 1 - color: "#2C2D30" - } + + DividerType {} } } diff --git a/client/ui/qml/Components/ContainersPageHomeListView.qml b/client/ui/qml/Components/ContainersPageHomeListView.qml index 309b4217..f8b5101e 100644 --- a/client/ui/qml/Components/ContainersPageHomeListView.qml +++ b/client/ui/qml/Components/ContainersPageHomeListView.qml @@ -116,4 +116,3 @@ ListView { } } } - diff --git a/client/ui/qml/Components/PageSettingsContainersListView.qml b/client/ui/qml/Components/PageSettingsContainersListView.qml new file mode 100644 index 00000000..2a59b8b2 --- /dev/null +++ b/client/ui/qml/Components/PageSettingsContainersListView.qml @@ -0,0 +1,107 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 + +import "../Controls2" +import "../Controls2/TextTypes" + + +ListView { + id: root + + height: root.contentItem.height + + clip: true + + ButtonGroup { + id: containersRadioButtonGroup + } + + delegate: Item { + implicitWidth: parent.width + implicitHeight: containerRadioButton.implicitHeight + + RadioButton { + id: containerRadioButton + + implicitWidth: parent.width + implicitHeight: containerRadioButtonContent.implicitHeight + + hoverEnabled: true + + ButtonGroup.group: containersRadioButtonGroup + + checked: isDefault + + indicator: Rectangle { + anchors.fill: parent + color: containerRadioButton.hovered ? Qt.rgba(1, 1, 1, 0.08) : "transparent" + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + + checkable: isInstalled + + RowLayout { + id: containerRadioButtonContent + anchors.fill: parent + + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + z: 1 + + ColumnLayout { + Layout.topMargin: 20 + Layout.bottomMargin: 20 + + ListItemTitleType { + Layout.fillWidth: true + + text: name + } + + CaptionTextType { + Layout.fillWidth: true + + text: description + color: "#878B91" + } + } + + Image { + source: isInstalled ? "qrc:/images/controls/chevron-right.svg" : "qrc:/images/controls/download.svg" + + width: 24 + height: 24 + + Layout.rightMargin: 8 + } + } + + onClicked: { + if (isInstalled) { +// isDefault = true +// root.currentIndex = index + } else { + ContainersModel.setCurrentlyInstalledContainerIndex(root.model.mapToSource(index)) + InstallController.setShouldCreateServer(false) + goToPage(PageEnum.PageSetupWizardProtocolSettings) + } + } + + MouseArea { + anchors.fill: containerRadioButton + cursorShape: Qt.PointingHandCursor + enabled: false + } + } + } +} diff --git a/client/ui/qml/Controls2/DividerType.qml b/client/ui/qml/Controls2/DividerType.qml new file mode 100644 index 00000000..6341807a --- /dev/null +++ b/client/ui/qml/Controls2/DividerType.qml @@ -0,0 +1,8 @@ +import QtQuick +import QtQuick.Layouts + +Rectangle { + Layout.fillWidth: true + height: 1 + color: "#2C2D30" +} diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml index 2530f583..e4e711a6 100644 --- a/client/ui/qml/Controls2/LabelWithButtonType.qml +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -2,6 +2,8 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import "TextTypes" + Item { id: root @@ -13,12 +15,16 @@ Item { property alias buttonImage: button.image property string iconImage + property string textColor: "#d7d8db" + implicitWidth: content.implicitWidth implicitHeight: content.implicitHeight RowLayout { id: content anchors.fill: parent + anchors.leftMargin: 16 + anchors.rightMargin: 16 Image { id: icon @@ -28,34 +34,28 @@ Item { } ColumnLayout { - Text { - font.family: "PT Root UI VF" - font.styleName: "normal" - font.pixelSize: 18 - color: "#d7d8db" + ListItemTitleType { text: root.text - wrapMode: Text.WordWrap + color: textColor Layout.fillWidth: true - height: 22 + Layout.topMargin: 16 + Layout.bottomMargin: description.visible ? 0 : 16 horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter } - Text { - font.family: "PT Root UI VF" - font.styleName: "normal" - font.pixelSize: 13 - font.letterSpacing: 0.02 + CaptionTextType { + id: description + color: "#878B91" text: root.descriptionText - wrapMode: Text.WordWrap visible: root.descriptionText !== "" Layout.fillWidth: true - height: 16 + Layout.bottomMargin: 16 horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter @@ -88,21 +88,45 @@ Item { } } } + + Rectangle { + id: background + anchors.fill: root + color: "transparent" + + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor hoverEnabled: true onEntered: { - imageBackground.color = button.hoveredColor + if (buttonImage) { + imageBackground.color = button.hoveredColor + } else { + background.color = button.hoveredColor + } } onExited: { - imageBackground.color = button.defaultColor + if (buttonImage) { + imageBackground.color = button.defaultColor + } else { + background.color = button.defaultColor + } } onPressedChanged: { - imageBackground.color = pressed ? button.pressedColor : entered ? button.hoveredColor : button.defaultColor + if (buttonImage) { + imageBackground.color = pressed ? button.pressedColor : entered ? button.hoveredColor : button.defaultColor + } else { + background.color = pressed ? button.pressedColor : entered ? button.hoveredColor : button.defaultColor + } } onClicked: { diff --git a/client/ui/qml/Controls2/TextTypes/ListItemTitleType.qml b/client/ui/qml/Controls2/TextTypes/ListItemTitleType.qml new file mode 100644 index 00000000..23069db2 --- /dev/null +++ b/client/ui/qml/Controls2/TextTypes/ListItemTitleType.qml @@ -0,0 +1,12 @@ +import QtQuick + +Text { + height: 21.6 + + color: "#D7D8DB" + font.pixelSize: 18 + font.weight: Font.Normal + font.family: "PT Root UI VF" + + wrapMode: Text.WordWrap +} diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index d5455849..1fba3bff 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -37,21 +37,6 @@ PageType { } } - Connections { - target: InstallController - - function onInstallContainerFinished() { - goToStartPage() - menu.visible = true - containersDropDown.menuVisible = true - } - - function onInstallServerFinished() { - goToStartPage() - menu.visible = true - } - } - Rectangle { id: buttonBackground anchors.fill: buttonContent @@ -166,6 +151,10 @@ PageType { ValueFilter { roleName: "serviceType" value: ProtocolEnum.Vpn + }, + ValueFilter { + roleName: "isSupported" + value: true } ] } diff --git a/client/ui/qml/Pages2/PageSettings.qml b/client/ui/qml/Pages2/PageSettings.qml index 9d5b7442..57c252b4 100644 --- a/client/ui/qml/Pages2/PageSettings.qml +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -5,6 +5,7 @@ import QtQuick.Layouts import SortFilterProxyModel 0.2 import PageEnum 1.0 +import ProtocolEnum 1.0 import ContainerProps 1.0 import ProtocolProps 1.0 @@ -12,6 +13,7 @@ import "./" import "../Controls2" import "../Controls2/TextTypes" import "../Config" +import "../Components" PageType { id: root @@ -27,35 +29,28 @@ PageType { ] } - FlickableType { - id: fl - anchors.fill: parent - contentHeight: content.height - ColumnLayout { id: content - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + anchors.fill: parent spacing: 16 Repeater { + id: header model: proxyServersModel delegate: HeaderType { - id: header - Layout.fillWidth: true Layout.topMargin: 20 Layout.leftMargin: 16 Layout.rightMargin: 16 - actionButtonImage: "qrc:/images/controls/plus.svg" + actionButtonImage: "qrc:/images/controls/edit-3.svg" backButtonImage: "qrc:/images/controls/arrow-left.svg" headerText: name + descriptionText: hostName actionButtonFunction: function() { connectionTypeSelection.visible = true @@ -66,6 +61,82 @@ PageType { } } } + + TabBar { + id: tabBar + + Layout.fillWidth: true + + background: Rectangle { + color: "transparent" + } + + TabButtonType { + isSelected: tabBar.currentIndex === 0 + text: qsTr("Protocols") + } + TabButtonType { + isSelected: tabBar.currentIndex === 1 + text: qsTr("Services") + } + TabButtonType { + isSelected: tabBar.currentIndex === 2 + text: qsTr("Data") + } + } + + StackLayout { + id: stackLayout + currentIndex: tabBar.currentIndex + + Layout.fillWidth: true + height: root.height + + StackView { + id: protocolsStackView + initialItem: PageSettingsContainersListView { + model: SortFilterProxyModel { + sourceModel: ContainersModel + filters: [ + ValueFilter { + roleName: "serviceType" + value: ProtocolEnum.Vpn + }, + ValueFilter { + roleName: "isSupported" + value: true + } + ] + } + } + } + + StackView { + id: servicesStackView + initialItem: PageSettingsContainersListView { + model: SortFilterProxyModel { + sourceModel: ContainersModel + filters: [ + ValueFilter { + roleName: "serviceType" + value: ProtocolEnum.Other + }, + ValueFilter { + roleName: "isSupported" + value: true + } + ] + } + } + } + + StackView { + id: dataStackView + initialItem: PageSettingsData { + + } + } + } } - } +// } } diff --git a/client/ui/qml/Pages2/PageSettingsData.qml b/client/ui/qml/Pages2/PageSettingsData.qml new file mode 100644 index 00000000..74293aa5 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsData.qml @@ -0,0 +1,67 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 + +import "../Controls2" +import "../Controls2/TextTypes" + +PageType { + id: root + + FlickableType { + id: fl + 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 + + LabelWithButtonType { + Layout.fillWidth: true + + text: "Clear Amnezia cache" + descriptionText: "May be needed when changing other settings" + + clickedFunction: function() { + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: "Remove server from application" + textColor: "#EB5757" + + clickedFunction: function() { + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: "Clear server from Amnezia software" + textColor: "#EB5757" + + clickedFunction: function() { + ContainersModel.removeAllContainers() + } + } + + DividerType {} + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index 79e24f75..2344b7e8 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -57,11 +57,6 @@ PageType { anchors.left: parent.left anchors.right: parent.right - anchors.leftMargin: 16 - anchors.rightMargin: 16 - - spacing: 16 - ListView { id: servers width: parent.width @@ -85,8 +80,6 @@ PageType { LabelWithButtonType { id: server Layout.fillWidth: true - Layout.topMargin: 16 - Layout.bottomMargin: 16 text: name descriptionText: hostName @@ -98,11 +91,7 @@ PageType { } } - Rectangle { - Layout.fillWidth: true - height: 1 - color: "#2C2D30" - } + DividerType {} } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 53a40889..25b88f76 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -33,14 +33,12 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 - - spacing: 16 HeaderType { Layout.fillWidth: true Layout.topMargin: 20 + Layout.rightMargin: 16 + Layout.leftMargin: 16 backButtonImage: "qrc:/images/controls/arrow-left.svg" @@ -52,6 +50,8 @@ PageType { Header2TextType { Layout.fillWidth: true Layout.topMargin: 32 + Layout.rightMargin: 16 + Layout.leftMargin: 16 text: "Что у вас есть?" } @@ -75,11 +75,8 @@ PageType { } } } - Rectangle { - Layout.fillWidth: true - height: 1 - color: "#2C2D30" - } + + DividerType {} //todo ifdef mobile platforms LabelWithButtonType { @@ -93,11 +90,7 @@ PageType { } } - Rectangle { - Layout.fillWidth: true - height: 1 - color: "#2C2D30" - } + DividerType {} LabelWithButtonType { Layout.fillWidth: true @@ -111,11 +104,7 @@ PageType { } } - Rectangle { - Layout.fillWidth: true - height: 1 - color: "#2C2D30" - } + DividerType {} } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index fd8aac6b..006743d9 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -23,6 +23,14 @@ PageType { closePage() PageController.showErrorMessage(errorMessage) } + + function onInstallContainerFinished() { + goToStartPage() + } + + function onInstallServerFinished() { + goToStartPage() + } } SortFilterProxyModel { diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index bffe09cb..cdacaf04 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -75,12 +75,12 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right + anchors.rightMargin: -16 + anchors.leftMargin: -16 LabelWithButtonType { id: container Layout.fillWidth: true - Layout.topMargin: 16 - Layout.bottomMargin: 16 text: name descriptionText: description @@ -92,11 +92,7 @@ PageType { } } - Rectangle { - Layout.fillWidth: true - height: 1 - color: "#2C2D30" - } + DividerType {} } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index 4d0f4c5c..804e145c 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -26,8 +26,9 @@ PageType { target: InstallController function onInstallServerFinished() { + //todo add smt like changeStartPage goToStartPage() -// goToPage(PageEnum.PageStart) + goToPage(PageEnum.PageStart) } } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 19ea3bc4..c50d4591 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -38,8 +38,9 @@ PageType { height: root.height - tabBar.implicitHeight StackView { - id: homeStackView - initialItem: "PageHome.qml" //PageController.getPagePath(PageEnum.PageSettingsServersList) + initialItem: PageHome { + id: pageHome + } } Item { @@ -47,8 +48,9 @@ PageType { } StackView { - id: settingsStackView - initialItem: "PageSettingsServersList.qml" //PageController.getPagePath(PageEnum.PageSettingsServersList) + initialItem: PageSettingsServersList { + id: pageSettingsServersList + } } } @@ -72,6 +74,9 @@ PageType { TabImageButtonType { isSelected: tabBar.currentIndex === 0 image: "qrc:/images/controls/home.svg" + onClicked: { + pageSettingsServersList.goToStartPage() + } } TabImageButtonType { isSelected: tabBar.currentIndex === 1 @@ -80,6 +85,9 @@ PageType { TabImageButtonType { isSelected: tabBar.currentIndex === 2 image: "qrc:/images/controls/settings-2.svg" + onClicked: { + pageHome.goToStartPage() + } } } From de0cd976debb0691e69b77e1f0cc62b4e17d12de Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 1 Jun 2023 11:25:33 +0800 Subject: [PATCH 024/278] added page transition effects - added functionality for buttons on PageSettingsServerData page --- client/amnezia_application.cpp | 2 +- client/images/controls/amnezia.svg | 3 + client/images/controls/app.svg | 6 + client/images/controls/radio.svg | 7 + client/images/controls/save.svg | 5 + client/images/controls/server.svg | 6 + client/resources.qrc | 18 +- .../ui/controllers/connectionController.cpp | 1 - client/ui/controllers/installController.cpp | 5 +- client/ui/controllers/pageController.cpp | 3 +- client/ui/controllers/pageController.h | 7 +- client/ui/models/containers_model.cpp | 11 +- client/ui/models/containers_model.h | 1 + client/ui/models/servers_model.cpp | 51 ++++-- client/ui/models/servers_model.h | 5 +- client/ui/pages.h | 9 +- client/ui/qml/Components/ConnectButton.qml | 14 +- .../ConnectionTypeSelectionDrawer.qml | 6 +- ...istView.qml => HomeContainersListView.qml} | 59 +----- ...iew.qml => SettingsContainersListView.qml} | 3 +- client/ui/qml/Controls2/DrawerType.qml | 20 +++ client/ui/qml/Controls2/DropDownType.qml | 6 +- client/ui/qml/Controls2/PageType.qml | 5 +- client/ui/qml/Controls2/StackViewType.qml | 61 +++++++ client/ui/qml/Controls2/SwitcherType.qml | 1 + .../ui/qml/Controls2/VerticalRadioButton.qml | 61 +++++-- client/ui/qml/Pages2/PageHome.qml | 121 ++++++------- client/ui/qml/Pages2/PageSettings.qml | 169 +++++++----------- ...ngsData.qml => PageSettingsServerData.qml} | 13 ++ .../ui/qml/Pages2/PageSettingsServerInfo.qml | 134 ++++++++++++++ .../Pages2/PageSettingsServerProtocols.qml | 38 ++++ .../qml/Pages2/PageSettingsServerServices.qml | 38 ++++ .../ui/qml/Pages2/PageSettingsServersList.qml | 7 +- .../qml/Pages2/PageSetupWizardInstalling.qml | 16 ++ client/ui/qml/Pages2/PageSetupWizardStart.qml | 10 -- client/ui/qml/Pages2/PageStart.qml | 31 ++-- client/ui/qml/main2.qml | 24 ++- client/ui/uilogic.cpp | 14 +- 38 files changed, 656 insertions(+), 335 deletions(-) create mode 100644 client/images/controls/amnezia.svg create mode 100644 client/images/controls/app.svg create mode 100644 client/images/controls/radio.svg create mode 100644 client/images/controls/save.svg create mode 100644 client/images/controls/server.svg rename client/ui/qml/Components/{ContainersPageHomeListView.qml => HomeContainersListView.qml} (52%) rename client/ui/qml/Components/{PageSettingsContainersListView.qml => SettingsContainersListView.qml} (97%) create mode 100644 client/ui/qml/Controls2/DrawerType.qml create mode 100644 client/ui/qml/Controls2/StackViewType.qml rename client/ui/qml/Pages2/{PageSettingsData.qml => PageSettingsServerData.qml} (67%) create mode 100644 client/ui/qml/Pages2/PageSettingsServerInfo.qml create mode 100644 client/ui/qml/Pages2/PageSettingsServerProtocols.qml create mode 100644 client/ui/qml/Pages2/PageSettingsServerServices.qml diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 9fdddc86..c8bdcf3b 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -179,7 +179,7 @@ void AmneziaApplication::registerTypes() qRegisterMetaType("PageProtocolLogicBase *"); - declareQmlPageEnum(); +// declareQmlPageEnum(); declareQmlProtocolEnum(); declareQmlContainerEnum(); diff --git a/client/images/controls/amnezia.svg b/client/images/controls/amnezia.svg new file mode 100644 index 00000000..0a6017dd --- /dev/null +++ b/client/images/controls/amnezia.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/images/controls/app.svg b/client/images/controls/app.svg new file mode 100644 index 00000000..87775cd1 --- /dev/null +++ b/client/images/controls/app.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/client/images/controls/radio.svg b/client/images/controls/radio.svg new file mode 100644 index 00000000..27731814 --- /dev/null +++ b/client/images/controls/radio.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/client/images/controls/save.svg b/client/images/controls/save.svg new file mode 100644 index 00000000..442ff72c --- /dev/null +++ b/client/images/controls/save.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/images/controls/server.svg b/client/images/controls/server.svg new file mode 100644 index 00000000..52aad656 --- /dev/null +++ b/client/images/controls/server.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/client/resources.qrc b/client/resources.qrc index ca02df7d..db53165c 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -228,16 +228,26 @@ images/controls/download.svg ui/qml/Controls2/ProgressBarType.qml ui/qml/Components/ConnectionTypeSelectionDrawer.qml - ui/qml/Components/ContainersPageHomeListView.qml + ui/qml/Components/HomeContainersListView.qml ui/qml/Controls2/TextTypes/CaptionTextType.qml images/controls/settings.svg - ui/qml/Pages2/PageSettings.qml + ui/qml/Pages2/PageSettingsServerInfo.qml ui/qml/Controls2/PageType.qml ui/qml/Controls2/PopupType.qml images/controls/edit-3.svg - ui/qml/Pages2/PageSettingsData.qml - ui/qml/Components/PageSettingsContainersListView.qml + ui/qml/Pages2/PageSettingsServerData.qml + ui/qml/Components/SettingsContainersListView.qml ui/qml/Controls2/TextTypes/ListItemTitleType.qml ui/qml/Controls2/DividerType.qml + ui/qml/Controls2/DrawerType.qml + ui/qml/Controls2/StackViewType.qml + ui/qml/Pages2/PageSettings.qml + images/controls/amnezia.svg + images/controls/app.svg + images/controls/radio.svg + images/controls/save.svg + images/controls/server.svg + ui/qml/Pages2/PageSettingsServerProtocols.qml + ui/qml/Pages2/PageSettingsServerServices.qml diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index 58fef373..617fd5ee 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -74,4 +74,3 @@ bool ConnectionController::closeVpnConnection() emit disconnectFromVpn(); m_isConnected = false; } - diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 31f58a2a..15a24c31 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -49,8 +49,9 @@ void InstallController::installServer(DockerContainer container, QJsonObject& co server.insert(config_key::containers, QJsonArray{ config }); server.insert(config_key::defaultContainer, ContainerProps::containerToString(container)); - m_settings->addServer(server); - m_settings->setDefaultServer(m_settings->serversCount() - 1); + m_serversModel->addServer(server); + auto newServerIndex = m_serversModel->index(m_serversModel->getServersCount() - 1); + m_serversModel->setData(newServerIndex, true, ServersModel::ServersModelRoles::IsDefaultRole); emit installServerFinished(); return; diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/pageController.cpp index 63c3ba58..e49177a5 100644 --- a/client/ui/controllers/pageController.cpp +++ b/client/ui/controllers/pageController.cpp @@ -9,7 +9,8 @@ QString PageController::getInitialPage() { if (m_serversModel->getServersCount()) { if (m_serversModel->getDefaultServerIndex() < 0) { - m_serversModel->setDefaultServerIndex(0); + auto defaultServerIndex = m_serversModel->index(0); + m_serversModel->setData(defaultServerIndex, true, ServersModel::ServersModelRoles::IsDefaultRole); } return getPagePath(PageLoader::PageEnum::PageStart); } else { diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index f37edca9..1d041deb 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -9,7 +9,10 @@ namespace PageLoader { Q_NAMESPACE - enum class PageEnum { PageStart = 0, PageHome, PageSettings, PageShare, + enum class PageEnum { PageStart = 0, PageHome, PageShare, + + PageSettingsServersList, PageSettings, PageSettingsServerData, PageSettingsServerInfo, + PageSettingsServerProtocols, PageSettingsServerServices, PageSetupWizardStart, PageTest, PageSetupWizardCredentials, PageSetupWizardProtocols, PageSetupWizardEasy, PageSetupWizardProtocolSettings, PageSetupWizardInstalling, PageSetupWizardConfigSource, @@ -41,6 +44,8 @@ public slots: signals: void goToPageHome(); + void restorePageHomeState(bool isContainerInstalled = false); + void replaceStartPage(); void showErrorMessage(QString errorMessage); private: diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index 4dc7010e..b240878d 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -86,6 +86,7 @@ void ContainersModel::setCurrentlyProcessedServerIndex(int index) beginResetModel(); m_currentlyProcessedServerIndex = index; endResetModel(); + emit defaultContainerChanged(); } void ContainersModel::setCurrentlyInstalledContainerIndex(int index) @@ -115,7 +116,7 @@ void ContainersModel::removeAllContainers() auto errorCode = serverController.removeAllContainers(m_settings->serverCredentials(m_currentlyProcessedServerIndex)); if (errorCode == ErrorCode::NoError) { - beginResetModel(); + beginResetModel(); m_settings->setContainers(m_currentlyProcessedServerIndex, {}); m_settings->setDefaultContainer(m_currentlyProcessedServerIndex, DockerContainer::None); endResetModel(); @@ -124,6 +125,14 @@ void ContainersModel::removeAllContainers() //todo process errors } +void ContainersModel::clearCachedProfiles() +{ + const auto &containers = m_settings->containers(m_currentlyProcessedServerIndex); + for (DockerContainer container : containers.keys()) { + m_settings->clearLastConnectionConfig(m_currentlyProcessedServerIndex, container); + } +} + QHash ContainersModel::roleNames() const { QHash roles; roles[NameRole] = "name"; diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index 642b6680..3ce7bd6b 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -49,6 +49,7 @@ public slots: int getCurrentlyInstalledContainerIndex(); void removeAllContainers(); + void clearCachedProfiles(); protected: QHash roleNames() const override; diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 884009e8..d05bb695 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -17,15 +17,14 @@ bool ServersModel::setData(const QModelIndex &index, const QVariant &value, int || index.row() >= static_cast(m_settings->serversCount())) { return false; } -// if (role == DescRole) { -// return m_data[index.row()].desc; -// } -// if (role == AddressRole) { -// return m_data[index.row()].address; -// } -// if (role == IsDefaultRole) { -// return m_data[index.row()].isDefault; -// } + + switch (role) { + case IsDefaultRole: m_settings->setDefaultServer(index.row()); + default: return true; + } + + emit dataChanged(index, index); + return true; } QVariant ServersModel::data(const QModelIndex &index, int role) const @@ -58,14 +57,6 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const return QVariant(); } -//todo mode to setData? -void ServersModel::setDefaultServerIndex(int index) -{ - // beginResetModel(); - m_settings->setDefaultServer(index); - // endResetModel(); -} - const int ServersModel::getDefaultServerIndex() { return m_settings->defaultServerIndex(); @@ -81,14 +72,38 @@ void ServersModel::setCurrentlyProcessedServerIndex(int index) m_currenlyProcessedServerIndex = index; } +bool ServersModel::isDefaultServerCurrentlyProcessed() +{ + return m_settings->defaultServerIndex() == m_currenlyProcessedServerIndex; +} + ServerCredentials ServersModel::getCurrentlyProcessedServerCredentials() { return qvariant_cast(data(index(m_currenlyProcessedServerIndex), CredentialsRole)); } -void ServersModel::addServer() +void ServersModel::addServer(const QJsonObject &server) { + beginResetModel(); + m_settings->addServer(server); + endResetModel(); +} +void ServersModel::removeServer() +{ + beginResetModel(); + m_settings->removeServer(m_currenlyProcessedServerIndex); + + if (m_settings->defaultServerIndex() == m_currenlyProcessedServerIndex) { + m_settings->setDefaultServer(0); + } else if (m_settings->defaultServerIndex() > m_currenlyProcessedServerIndex) { + m_settings->setDefaultServer(m_settings->defaultServerIndex() - 1); + } + + if (m_settings->serversCount() == 0) { + m_settings->setDefaultServer(-1); + } + endResetModel(); } QHash ServersModel::roleNames() const { diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index ae1ec8d9..54ac5ef4 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -31,15 +31,16 @@ public: QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; public slots: - void setDefaultServerIndex(int index); const int getDefaultServerIndex(); + bool isDefaultServerCurrentlyProcessed(); const int getServersCount(); void setCurrentlyProcessedServerIndex(int index); ServerCredentials getCurrentlyProcessedServerCredentials(); - void addServer(); + void addServer(const QJsonObject &server); + void removeServer(); protected: QHash roleNames() const override; diff --git a/client/ui/pages.h b/client/ui/pages.h index 82c0d409..f3d045b2 100644 --- a/client/ui/pages.h +++ b/client/ui/pages.h @@ -26,14 +26,7 @@ enum class Page { Start = 0, NewServer, NewServerProtocols, Vpn, GeneralSettings, AppSettings, NetworkSettings, ServerSettings, ServerContainers, ServersList, ShareConnection, Sites, ProtocolSettings, ProtocolShare, QrDecoder, QrDecoderIos, About, ViewConfig, - AdvancedServerSettings, ClientManagement, ClientInfo, - - PageSetupWizardStart, PageTest, PageSetupWizardCredentials, PageSetupWizardProtocols, PageSetupWizardEasy, - PageSetupWizardProtocolSettings, PageSetupWizardInstalling, PageSetupWizardConfigSource, PageSetupWizardTextKey, - - PageSettings, PageSettingsServersList, - - PageStart, PageHome, PageShare}; + AdvancedServerSettings, ClientManagement, ClientInfo}; Q_ENUM_NS(Page) static void declareQmlPageEnum() { diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml index 5002d5bd..73accef4 100644 --- a/client/ui/qml/Components/ConnectButton.qml +++ b/client/ui/qml/Components/ConnectButton.qml @@ -7,7 +7,7 @@ import ConnectionState 1.0 Button { id: root - text: "Подключиться" + text: qsTr("Connect") background: Image { id: border @@ -60,37 +60,37 @@ Button { case ConnectionState.Disconnected: { console.log("Disconnected") connectionProccess.running = false - root.text = "Подключиться" + root.text = qsTr("Connect") break } case ConnectionState.Preparing: { console.log("Preparing") connectionProccess.running = true - root.text = "Подключение..." + root.text = qsTr("Connection...") break } case ConnectionState.Connecting: { console.log("Connecting") connectionProccess.running = true - root.text = "Подключение..." + root.text = qsTr("Connection...") break } case ConnectionState.Connected: { console.log("Connected") connectionProccess.running = false - root.text = "Подключено" + root.text = qsTr("Connected") break } case ConnectionState.Disconnecting: { console.log("Disconnecting") connectionProccess.running = true - root.text = "Отключение..." + root.text = qsTr("Disconnection...") break } case ConnectionState.Reconnecting: { console.log("Reconnecting") connectionProccess.running = true - root.text = "Переподключение..." + root.text = qsTr("Reconnection...") break } case ConnectionState.Error: { diff --git a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml index 2e6b3f2e..d80d5e5a 100644 --- a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml +++ b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml @@ -8,16 +8,12 @@ import "../Controls2" import "../Controls2/TextTypes" import "../Config" -Drawer { +DrawerType { id: root - edge: Qt.BottomEdge width: parent.width height: parent.height * 0.4375 - clip: true - modal: true - background: Rectangle { anchors.fill: parent anchors.bottomMargin: -radius diff --git a/client/ui/qml/Components/ContainersPageHomeListView.qml b/client/ui/qml/Components/HomeContainersListView.qml similarity index 52% rename from client/ui/qml/Components/ContainersPageHomeListView.qml rename to client/ui/qml/Components/HomeContainersListView.qml index f8b5101e..c3e98fbb 100644 --- a/client/ui/qml/Components/ContainersPageHomeListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -29,64 +29,23 @@ ListView { implicitWidth: rootWidth implicitHeight: containerRadioButton.implicitHeight - RadioButton { + VerticalRadioButton { id: containerRadioButton - implicitWidth: parent.width - implicitHeight: containerRadioButtonContent.implicitHeight + anchors.fill: parent + anchors.rightMargin: 16 + anchors.leftMargin: 16 - hoverEnabled: true + text: name + descriptionText: description ButtonGroup.group: containersRadioButtonGroup - checked: isDefault - - indicator: Rectangle { - anchors.fill: parent - color: containerRadioButton.hovered ? "#2C2D30" : "#1C1D21" - - Behavior on color { - PropertyAnimation { duration: 200 } - } - } + imageSource: "qrc:/images/controls/download.svg" + showImage: !isInstalled checkable: isInstalled - - RowLayout { - id: containerRadioButtonContent - anchors.fill: parent - - anchors.rightMargin: 16 - anchors.leftMargin: 16 - - z: 1 - - Image { - source: isInstalled ? "qrc:/images/controls/check.svg" : "qrc:/images/controls/download.svg" - visible: isInstalled ? containerRadioButton.checked : true - - width: 24 - height: 24 - - Layout.rightMargin: 8 - } - - Text { - id: containerRadioButtonText - - text: name - color: "#D7D8DB" - font.pixelSize: 16 - font.weight: 400 - font.family: "PT Root UI VF" - - height: 24 - - Layout.fillWidth: true - Layout.topMargin: 20 - Layout.bottomMargin: 20 - } - } + checked: isDefault onClicked: { if (checked) { diff --git a/client/ui/qml/Components/PageSettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml similarity index 97% rename from client/ui/qml/Components/PageSettingsContainersListView.qml rename to client/ui/qml/Components/SettingsContainersListView.qml index 2a59b8b2..5175fd49 100644 --- a/client/ui/qml/Components/PageSettingsContainersListView.qml +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -14,6 +14,7 @@ import "../Controls2/TextTypes" ListView { id: root + width: parent.width height: root.contentItem.height clip: true @@ -23,7 +24,7 @@ ListView { } delegate: Item { - implicitWidth: parent.width + implicitWidth: root.width implicitHeight: containerRadioButton.implicitHeight RadioButton { diff --git a/client/ui/qml/Controls2/DrawerType.qml b/client/ui/qml/Controls2/DrawerType.qml new file mode 100644 index 00000000..bede3700 --- /dev/null +++ b/client/ui/qml/Controls2/DrawerType.qml @@ -0,0 +1,20 @@ +import QtQuick +import QtQuick.Controls + +Drawer { + edge: Qt.BottomEdge + + clip: true + modal: true + + enter: Transition { + SmoothedAnimation { + velocity: 4 + } + } + exit: Transition { + SmoothedAnimation { + velocity: 4 + } + } +} diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 2b8986b5..5ac50fa7 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -111,16 +111,12 @@ Item { } } - Drawer { + DrawerType { id: menu - edge: Qt.BottomEdge width: parent.width height: parent.height * 0.9 - clip: true - modal: true - background: Rectangle { anchors.fill: parent anchors.bottomMargin: -radius diff --git a/client/ui/qml/Controls2/PageType.qml b/client/ui/qml/Controls2/PageType.qml index 4eb51753..046201b7 100644 --- a/client/ui/qml/Controls2/PageType.qml +++ b/client/ui/qml/Controls2/PageType.qml @@ -8,10 +8,11 @@ Item { property StackView stackView: StackView.view function goToPage(page, slide = true) { + var pagePath = PageController.getPagePath(page) if (slide) { - root.stackView.push(PageController.getPagePath(page), {}, StackView.PushTransition) + root.stackView.push(pagePath, { "objectName" : pagePath }, StackView.PushTransition) } else { - root.stackView.push(PageController.getPagePath(page), {}, StackView.Immediate) + root.stackView.push(pagePath, { "objectName" : pagePath }, StackView.Immediate) } } diff --git a/client/ui/qml/Controls2/StackViewType.qml b/client/ui/qml/Controls2/StackViewType.qml new file mode 100644 index 00000000..e2646e45 --- /dev/null +++ b/client/ui/qml/Controls2/StackViewType.qml @@ -0,0 +1,61 @@ +import QtQuick +import QtQuick.Controls + +StackView { + id: root + + pushEnter: Transition { + PropertyAnimation { + property: "opacity" + from: 0 + to:1 + duration: 200 + } + } + + pushExit: Transition { + PropertyAnimation { + property: "opacity" + from: 1 + to:0 + duration: 200 + } + } + + popEnter: Transition { + PropertyAnimation { + property: "opacity" + from: 0 + to:1 + duration: 200 + } + } + + popExit: Transition { + PropertyAnimation { + property: "opacity" + from: 1 + to:0 + duration: 200 + } + } + + replaceEnter: Transition { + PropertyAnimation { + property: "opacity" + from: 0 + to:1 + duration: 200 + } + } + + replaceExit: Transition { + PropertyAnimation { + property: "opacity" + from: 1 + to:0 + duration: 200 + } + } +} + diff --git a/client/ui/qml/Controls2/SwitcherType.qml b/client/ui/qml/Controls2/SwitcherType.qml index b593ece8..e4df04fa 100644 --- a/client/ui/qml/Controls2/SwitcherType.qml +++ b/client/ui/qml/Controls2/SwitcherType.qml @@ -59,6 +59,7 @@ Switch { } } + contentItem: ColumnLayout { contentItem: ColumnLayout { id: content diff --git a/client/ui/qml/Controls2/VerticalRadioButton.qml b/client/ui/qml/Controls2/VerticalRadioButton.qml index 420051cd..50af3c79 100644 --- a/client/ui/qml/Controls2/VerticalRadioButton.qml +++ b/client/ui/qml/Controls2/VerticalRadioButton.qml @@ -3,6 +3,8 @@ import QtQuick.Controls import QtQuick.Layouts import Qt5Compat.GraphicalEffects +import "TextTypes" + RadioButton { id: root @@ -13,7 +15,8 @@ RadioButton { property string disabledColor: Qt.rgba(1, 1, 1, 0) property string selectedColor: Qt.rgba(1, 1, 1, 0) - property string textColor: "#0E0E11" + property string textColor: "#D7D8DB" + property string selectedTextColor: "#FBB26A" property string pressedBorderColor: Qt.rgba(251/255, 178/255, 106/255, 0.3) property string selectedBorderColor: "#FBB26A" @@ -26,11 +29,16 @@ RadioButton { property string defaultInnerCircleColor: "#FBB26A" + property string imageSource + property bool showImage + hoverEnabled: true indicator: Rectangle { id: background + anchors.verticalCenter: parent.verticalCenter + implicitWidth: 56 implicitHeight: 56 radius: 16 @@ -52,6 +60,16 @@ RadioButton { PropertyAnimation { duration: 200 } } + Image { + source: imageSource + visible: showImage + + anchors.centerIn: parent + + width: 24 + height: 24 + } + Rectangle { id: outerCircle @@ -59,6 +77,8 @@ RadioButton { height: 24 radius: 16 + visible: !showImage + anchors.centerIn: parent color: "transparent" @@ -120,34 +140,41 @@ RadioButton { contentItem: ColumnLayout { id: content - anchors.fill: parent + anchors.left: parent.left + anchors.right: parent.right anchors.leftMargin: 8 + background.width - Text { - text: root.text - wrapMode: Text.WordWrap - color: "#D7D8DB" - font.pixelSize: 16 - font.weight: 400 - font.family: "PT Root UI VF" + spacing: 4 + + ListItemTitleType { + text: root.text + + color: { + if (root.checked) { + return selectedTextColor + } + return textColor + } - height: 24 Layout.fillWidth: true + Layout.topMargin: 16 + Layout.bottomMargin: description.visible ? 0 : 16 + + Behavior on color { + PropertyAnimation { duration: 200 } + } } - Text { - font.family: "PT Root UI VF" - font.styleName: "normal" - font.pixelSize: 13 - font.letterSpacing: 0.02 + CaptionTextType { + id: description + color: "#878B91" text: root.descriptionText - wrapMode: Text.WordWrap visible: root.descriptionText !== "" Layout.fillWidth: true - height: 16 + Layout.bottomMargin: 16 } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 1fba3bff..0ecbafcc 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -37,6 +37,17 @@ PageType { } } + Connections { + target: PageController + + function onRestorePageHomeState(isContainerInstalled) { + menu.visible = true + if (isContainerInstalled) { + containersDropDown.menuVisible = true + } + } + } + Rectangle { id: buttonBackground anchors.fill: buttonContent @@ -96,16 +107,12 @@ PageType { } } - Drawer { + DrawerType { id: menu - edge: Qt.BottomEdge width: parent.width height: parent.height * 0.90 - clip: true - modal: true - background: Rectangle { anchors.fill: parent anchors.bottomMargin: -radius @@ -180,7 +187,7 @@ PageType { containersDropDown.menuVisible = true } - listView: ContainersPageHomeListView { + listView: HomeContainersListView { rootWidth: root.width model: proxyContainersModel @@ -251,81 +258,63 @@ PageType { property variant delegateData: model implicitWidth: serversMenuContent.width - implicitHeight: serverRadioButton.implicitHeight + implicitHeight: serverRadioButtonContent.implicitHeight - RadioButton { - id: serverRadioButton + ColumnLayout { + id: serverRadioButtonContent + anchors.fill: parent - implicitWidth: parent.width - implicitHeight: serverRadioButtonContent.implicitHeight + anchors.rightMargin: 16 + anchors.leftMargin: 16 - hoverEnabled: true - - checked: index === serversMenuContent.currentIndex - - ButtonGroup.group: serversRadioButtonGroup - - indicator: Rectangle { - anchors.fill: parent - color: serverRadioButton.hovered ? "#2C2D30" : "#1C1D21" - - Behavior on color { - PropertyAnimation { duration: 200 } - } - } + spacing: 0 RowLayout { - id: serverRadioButtonContent - anchors.fill: parent - - anchors.rightMargin: 16 - anchors.leftMargin: 16 - - z: 1 - - Image { - source: "qrc:/images/controls/check.svg" - visible: serverRadioButton.checked - width: 24 - height: 24 - - Layout.rightMargin: 8 - } - - Text { - id: serverRadioButtonText - - text: name - color: "#D7D8DB" - font.pixelSize: 16 - font.weight: 400 - font.family: "PT Root UI VF" - - height: 24 + VerticalRadioButton { + id: serverRadioButton Layout.fillWidth: true - Layout.topMargin: 20 - Layout.bottomMargin: 20 + + text: name + descriptionText: "description" + + checked: index === serversMenuContent.currentIndex + + ButtonGroup.group: serversRadioButtonGroup + + onClicked: { + serversMenuContent.currentIndex = index + + isDefault = true + ContainersModel.setCurrentlyProcessedServerIndex(index) + } + + MouseArea { + anchors.fill: serverRadioButton + cursorShape: Qt.PointingHandCursor + enabled: false + } } ImageButtonType { image: "qrc:/images/controls/settings.svg" -// onClicked: + implicitWidth: 56 + implicitHeight: 56 + + z: 1 + + onClicked: function() { + ServersModel.setCurrentlyProcessedServerIndex(index) + ContainersModel.setCurrentlyProcessedServerIndex(index) + goToPage(PageEnum.PageSettingsServerInfo) + menu.visible = false + } } } - onClicked: { - serversMenuContent.currentIndex = index - - ServersModel.setDefaultServerIndex(index) - ContainersModel.setCurrentlyProcessedServerIndex(index) - } - - MouseArea { - anchors.fill: serverRadioButton - cursorShape: Qt.PointingHandCursor - enabled: false + DividerType { + Layout.fillWidth: true } } } diff --git a/client/ui/qml/Pages2/PageSettings.qml b/client/ui/qml/Pages2/PageSettings.qml index 57c252b4..d3b87c19 100644 --- a/client/ui/qml/Pages2/PageSettings.qml +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -1,142 +1,109 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts - -import SortFilterProxyModel 0.2 +import QtQuick.Dialogs import PageEnum 1.0 -import ProtocolEnum 1.0 -import ContainerProps 1.0 -import ProtocolProps 1.0 import "./" import "../Controls2" import "../Controls2/TextTypes" import "../Config" -import "../Components" PageType { id: root - SortFilterProxyModel { - id: proxyServersModel - sourceModel: ServersModel - filters: [ - ValueFilter { - roleName: "isCurrentlyProcessed" - value: true - } - ] - } + FlickableType { + id: fl + anchors.top: root.top + anchors.bottom: root.bottom + contentHeight: content.height ColumnLayout { id: content - anchors.fill: parent + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right - spacing: 16 + HeaderType { + Layout.fillWidth: true + Layout.topMargin: 20 + Layout.rightMargin: 16 + Layout.leftMargin: 16 - Repeater { - id: header - model: proxyServersModel + headerText: qsTr("Settings") + } - delegate: HeaderType { - Layout.fillWidth: true - Layout.topMargin: 20 - Layout.leftMargin: 16 - Layout.rightMargin: 16 + LabelWithButtonType { + Layout.fillWidth: true + Layout.topMargin: 16 - actionButtonImage: "qrc:/images/controls/edit-3.svg" - backButtonImage: "qrc:/images/controls/arrow-left.svg" + text: qsTr("Servers") + buttonImage: "qrc:/images/controls/chevron-right.svg" + iconImage: "qrc:/images/controls/server.svg" - headerText: name - descriptionText: hostName - - actionButtonFunction: function() { - connectionTypeSelection.visible = true - } - - backButtonFunction: function() { - closePage() - } + clickedFunction: function() { + goToPage(PageEnum.PageSettingsServersList) } } - TabBar { - id: tabBar + DividerType {} + LabelWithButtonType { Layout.fillWidth: true - background: Rectangle { - color: "transparent" - } + text: qsTr("Connection") + buttonImage: "qrc:/images/controls/chevron-right.svg" + iconImage: "qrc:/images/controls/radio.svg" - TabButtonType { - isSelected: tabBar.currentIndex === 0 - text: qsTr("Protocols") - } - TabButtonType { - isSelected: tabBar.currentIndex === 1 - text: qsTr("Services") - } - TabButtonType { - isSelected: tabBar.currentIndex === 2 - text: qsTr("Data") + clickedFunction: function() { } } - StackLayout { - id: stackLayout - currentIndex: tabBar.currentIndex + DividerType {} + LabelWithButtonType { Layout.fillWidth: true - height: root.height - StackView { - id: protocolsStackView - initialItem: PageSettingsContainersListView { - model: SortFilterProxyModel { - sourceModel: ContainersModel - filters: [ - ValueFilter { - roleName: "serviceType" - value: ProtocolEnum.Vpn - }, - ValueFilter { - roleName: "isSupported" - value: true - } - ] - } - } - } + text: qsTr("Application") + buttonImage: "qrc:/images/controls/chevron-right.svg" + iconImage: "qrc:/images/controls/app.svg" - StackView { - id: servicesStackView - initialItem: PageSettingsContainersListView { - model: SortFilterProxyModel { - sourceModel: ContainersModel - filters: [ - ValueFilter { - roleName: "serviceType" - value: ProtocolEnum.Other - }, - ValueFilter { - roleName: "isSupported" - value: true - } - ] - } - } - } - - StackView { - id: dataStackView - initialItem: PageSettingsData { - - } + clickedFunction: function() { + goToPage(PageEnum.PageSetupWizardTextKey) } } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Backup") + buttonImage: "qrc:/images/controls/chevron-right.svg" + iconImage: "qrc:/images/controls/save.svg" + + clickedFunction: function() { + goToPage(PageEnum.PageSetupWizardTextKey) + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("About AmneziaVPN") + buttonImage: "qrc:/images/controls/chevron-right.svg" + iconImage: "qrc:/images/controls/amnezia.svg" + + clickedFunction: function() { + goToPage(PageEnum.PageSetupWizardTextKey) + } + } + + DividerType {} } -// } + } } diff --git a/client/ui/qml/Pages2/PageSettingsData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml similarity index 67% rename from client/ui/qml/Pages2/PageSettingsData.qml rename to client/ui/qml/Pages2/PageSettingsServerData.qml index 74293aa5..3ab76299 100644 --- a/client/ui/qml/Pages2/PageSettingsData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -33,6 +33,7 @@ PageType { descriptionText: "May be needed when changing other settings" clickedFunction: function() { + ContainersModel.clearCachedProfiles() } } @@ -45,6 +46,15 @@ PageType { textColor: "#EB5757" clickedFunction: function() { + if (ServersModel.isDefaultServerCurrentlyProcessed && ConnectionController.isConnected()) { + ConnectionController.closeVpnConnection() + } + ServersModel.removeServer() + if (!ServersModel.getServersCount()) { + PageController.replaceStartPage() + } else { + goToStartPage() + } } } @@ -57,6 +67,9 @@ PageType { textColor: "#EB5757" clickedFunction: function() { + if (ServersModel.isDefaultServerCurrentlyProcessed && ConnectionController.isConnected()) { + ConnectionController.closeVpnConnection() + } ContainersModel.removeAllContainers() } } diff --git a/client/ui/qml/Pages2/PageSettingsServerInfo.qml b/client/ui/qml/Pages2/PageSettingsServerInfo.qml new file mode 100644 index 00000000..29a0c3c1 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsServerInfo.qml @@ -0,0 +1,134 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 +import ContainerProps 1.0 +import ProtocolProps 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + SortFilterProxyModel { + id: proxyServersModel + sourceModel: ServersModel + filters: [ + ValueFilter { + roleName: "isCurrentlyProcessed" + value: true + } + ] + } + + ColumnLayout { + id: content + + anchors.fill: parent + + spacing: 16 + + Repeater { + id: header + model: proxyServersModel + + delegate: HeaderType { + Layout.fillWidth: true + Layout.topMargin: 20 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + actionButtonImage: "qrc:/images/controls/edit-3.svg" + backButtonImage: "qrc:/images/controls/arrow-left.svg" + + headerText: name + descriptionText: hostName + + actionButtonFunction: function() { + connectionTypeSelection.visible = true + } + + backButtonFunction: function() { + closePage() + } + } + } + + TabBar { + id: tabBar + + Layout.fillWidth: true + + background: Rectangle { + color: "transparent" + } + + TabButtonType { + isSelected: tabBar.currentIndex === 0 + text: qsTr("Protocols") +// onClicked: { +// tabBarStackView.goToTabBarPage(PageEnum.PageSettingsServerProtocols) +// } + } + TabButtonType { + isSelected: tabBar.currentIndex === 1 + text: qsTr("Services") +// onClicked: { +// tabBarStackView.goToTabBarPage(PageEnum.PageSettingsServerServices) +// } + } + TabButtonType { + isSelected: tabBar.currentIndex === 2 + text: qsTr("Data") +// onClicked: { +// tabBarStackView.goToTabBarPage(PageEnum.PageSettingsServerData) +// } + } + } + + StackLayout { + Layout.preferredWidth: root.width + Layout.preferredHeight: root.height - tabBar.implicitHeight - header.implicitHeight + + currentIndex: tabBar.currentIndex + + PageSettingsServerProtocols { + stackView: root.stackView + } + PageSettingsServerServices { + stackView: root.stackView + } + PageSettingsServerData { + stackView: root.stackView + } + } + +// StackViewType { +// id: tabBarStackView + +// Layout.preferredWidth: root.width +// Layout.preferredHeight: root.height - tabBar.implicitHeight - header.implicitHeight + +// function goToTabBarPage(page) { +// var pagePath = PageController.getPagePath(page) +// while (tabBarStackView.depth > 1) { +// tabBarStackView.pop() +// } +// tabBarStackView.replace(pagePath, { "objectName" : pagePath }) +// } + +// Component.onCompleted: { +// var pagePath = PageController.getPagePath(PageEnum.PageSettingsServerProtocols) +// tabBarStackView.push(pagePath, { "objectName" : pagePath }) +// } +// } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocols.qml b/client/ui/qml/Pages2/PageSettingsServerProtocols.qml new file mode 100644 index 00000000..e93aa528 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsServerProtocols.qml @@ -0,0 +1,38 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 +import ContainerProps 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + SortFilterProxyModel { + id: containersProxyModel + sourceModel: ContainersModel + filters: [ + ValueFilter { + roleName: "serviceType" + value: ProtocolEnum.Vpn + }, + ValueFilter { + roleName: "isSupported" + value: true + } + ] + } + + SettingsContainersListView { + model: containersProxyModel + } +} diff --git a/client/ui/qml/Pages2/PageSettingsServerServices.qml b/client/ui/qml/Pages2/PageSettingsServerServices.qml new file mode 100644 index 00000000..7351f585 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsServerServices.qml @@ -0,0 +1,38 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 +import ContainerProps 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + SortFilterProxyModel { + id: containersProxyModel + sourceModel: ContainersModel + filters: [ + ValueFilter { + roleName: "serviceType" + value: ProtocolEnum.Other + }, + ValueFilter { + roleName: "isSupported" + value: true + } + ] + } + + SettingsContainersListView { + model: containersProxyModel + } +} diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index 2344b7e8..63a40cc0 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -36,10 +36,6 @@ PageType { actionButtonFunction: function() { connectionTypeSelection.visible = true } - - backButtonFunction: function() { - PageController.goToPageHome() - } } ConnectionTypeSelectionDrawer { @@ -87,7 +83,8 @@ PageType { clickedFunction: function() { ServersModel.setCurrentlyProcessedServerIndex(index) - goToPage(PageEnum.PageSettings) + ContainersModel.setCurrentlyProcessedServerIndex(index) + goToPage(PageEnum.PageSettingsServerInfo) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index 006743d9..cf7b1d28 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -26,10 +26,26 @@ PageType { function onInstallContainerFinished() { goToStartPage() + if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) { + PageController.restorePageHomeState(true) + } else if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSettings)) { + goToPage(PageEnum.PageSettingsServersList, false) + goToPage(PageEnum.PageSettingsServerInfo, false) + } else { + goToPage(PageEnum.PageHome) + } } function onInstallServerFinished() { goToStartPage() + if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) { + PageController.restorePageHomeState() + } else if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSettings)) { + goToPage(PageEnum.PageSettingsServersList, false) + } else { + var pagePath = PageController.getPagePath(PageEnum.PageStart) + stackView.replace(pagePath, { "objectName" : pagePath }) + } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index 804e145c..6358f00d 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -22,16 +22,6 @@ PageType { } } - Connections { - target: InstallController - - function onInstallServerFinished() { - //todo add smt like changeStartPage - goToStartPage() - goToPage(PageEnum.PageStart) - } - } - FlickableType { id: fl anchors.top: root.top diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index c50d4591..44577037 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -17,6 +17,7 @@ PageType { function onGoToPageHome() { tabBar.currentIndex = 0 + tabBarStackView.goToTabBarPage(PageController.getPagePath(PageEnum.PageHome)) } function onShowErrorMessage(errorMessage) { @@ -25,9 +26,8 @@ PageType { } } - StackLayout { - id: stackLayout - currentIndex: tabBar.currentIndex + StackViewType { + id: tabBarStackView anchors.top: parent.top anchors.right: parent.right @@ -37,20 +37,15 @@ PageType { width: parent.width height: root.height - tabBar.implicitHeight - StackView { - initialItem: PageHome { - id: pageHome - } + function goToTabBarPage(page) { + var pagePath = PageController.getPagePath(page) + tabBarStackView.clear(StackView.PopTransition) + tabBarStackView.replace(pagePath, { "objectName" : pagePath }) } - Item { - - } - - StackView { - initialItem: PageSettingsServersList { - id: pageSettingsServersList - } + Component.onCompleted: { + var pagePath = PageController.getPagePath(PageEnum.PageHome) + tabBarStackView.push(pagePath, { "objectName" : pagePath }) } } @@ -70,12 +65,12 @@ PageType { color: "#1C1D21" } - TabImageButtonType { isSelected: tabBar.currentIndex === 0 image: "qrc:/images/controls/home.svg" onClicked: { - pageSettingsServersList.goToStartPage() + tabBarStackView.goToTabBarPage(PageEnum.PageHome) + ContainersModel.setCurrentlyProcessedServerIndex(ServersModel.getDefaultServerIndex()) } } TabImageButtonType { @@ -86,7 +81,7 @@ PageType { isSelected: tabBar.currentIndex === 2 image: "qrc:/images/controls/settings-2.svg" onClicked: { - pageHome.goToStartPage() + tabBarStackView.goToTabBarPage(PageEnum.PageSettings) } } } diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index 5ca1b345..bda1c709 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -4,8 +4,10 @@ import QtQuick.Controls import QtQuick.Layouts import PageType 1.0 +import PageEnum 1.0 import "Config" +import "Controls2" Window { id: root @@ -27,9 +29,27 @@ Window { color: "#0E0E11" } - StackView { + StackViewType { + id: rootStackView + anchors.fill: parent focus: true - initialItem: PageController.getInitialPage() + + Component.onCompleted: { + var pagePath = PageController.getPagePath(PageEnum.PageStart) + rootStackView.push(pagePath, { "objectName" : pagePath }) + } + } + + Connections { + target: PageController + + function onReplaceStartPage() { + var pagePath = PageController.getInitialPage() + while (rootStackView.depth > 1) { + rootStackView.pop() + } + rootStackView.replace(pagePath, { "objectName" : pagePath }) + } } } diff --git a/client/ui/uilogic.cpp b/client/ui/uilogic.cpp index 1cb43ee8..a0cac1cd 100644 --- a/client/ui/uilogic.cpp +++ b/client/ui/uilogic.cpp @@ -149,13 +149,13 @@ void UiLogic::initializeUiLogic() connect(m_notificationHandler, &NotificationHandler::connectRequested, pageLogic(), &VpnLogic::onConnect); connect(m_notificationHandler, &NotificationHandler::disconnectRequested, pageLogic(), &VpnLogic::onDisconnect); - if (m_settings->serversCount() > 0) { - if (m_settings->defaultServerIndex() < 0) m_settings->setDefaultServer(0); - emit goToPage(Page::PageStart, true, false); - } - else { - emit goToPage(Page::PageSetupWizardStart, true, false); - } +// if (m_settings->serversCount() > 0) { +// if (m_settings->defaultServerIndex() < 0) m_settings->setDefaultServer(0); +// emit goToPage(Page::PageStart, true, false); +// } +// else { +// emit goToPage(Page::PageSetupWizardStart, true, false); +// } m_selectedServerIndex = m_settings->defaultServerIndex(); From 420c33d3baa322b57e3b19f43083d730d4cc639c Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 5 Jun 2023 15:49:10 +0800 Subject: [PATCH 025/278] added PageSetupWizardViewConfig - added a popup with a question when deleting containers/servers - added import from code and import error handling --- client/core/defs.h | 5 +- client/core/errorstrings.cpp | 2 + client/images/controls/file-cog-2.svg | 11 ++ client/resources.qrc | 5 + .../ui/controllers/connectionController.cpp | 53 ++---- client/ui/controllers/connectionController.h | 14 +- client/ui/controllers/importController.cpp | 75 +++++---- client/ui/controllers/importController.h | 17 +- client/ui/controllers/pageController.h | 4 +- client/ui/qml/Components/ConnectButton.qml | 8 +- .../ConnectionTypeSelectionDrawer.qml | 14 -- .../qml/Components/HomeContainersListView.qml | 60 ++++--- client/ui/qml/Components/QuestionDrawer.qml | 77 +++++++++ client/ui/qml/Controls2/BackButtonType.qml | 55 +++++++ client/ui/qml/Controls2/DrawerType.qml | 14 ++ client/ui/qml/Controls2/DropDownType.qml | 39 ++--- client/ui/qml/Controls2/Header2Type.qml | 22 --- client/ui/qml/Controls2/HeaderType.qml | 22 --- client/ui/qml/Controls2/ProgressBarType.qml | 10 +- client/ui/qml/Controls2/SwitcherType.qml | 1 - .../qml/Controls2/TextFieldWithHeaderType.qml | 3 +- client/ui/qml/Pages2/PageDeinstalling.qml | 91 +++++++++++ client/ui/qml/Pages2/PageHome.qml | 14 -- client/ui/qml/Pages2/PageSettings.qml | 3 - .../ui/qml/Pages2/PageSettingsServerData.qml | 65 ++++++-- .../ui/qml/Pages2/PageSettingsServerInfo.qml | 58 ++----- .../ui/qml/Pages2/PageSettingsServersList.qml | 18 ++- .../Pages2/PageSetupWizardConfigSource.qml | 18 +-- .../qml/Pages2/PageSetupWizardCredentials.qml | 16 +- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 25 +-- .../qml/Pages2/PageSetupWizardInstalling.qml | 4 +- .../PageSetupWizardProtocolSettings.qml | 7 +- .../qml/Pages2/PageSetupWizardProtocols.qml | 5 +- .../ui/qml/Pages2/PageSetupWizardTextKey.qml | 22 +-- .../qml/Pages2/PageSetupWizardViewConfig.qml | 152 ++++++++++++++++++ client/ui/qml/Pages2/PageStart.qml | 2 + client/ui/qml/main2.qml | 2 +- 37 files changed, 701 insertions(+), 312 deletions(-) create mode 100644 client/images/controls/file-cog-2.svg create mode 100644 client/ui/qml/Components/QuestionDrawer.qml create mode 100644 client/ui/qml/Controls2/BackButtonType.qml create mode 100644 client/ui/qml/Pages2/PageDeinstalling.qml create mode 100644 client/ui/qml/Pages2/PageSetupWizardViewConfig.qml diff --git a/client/core/defs.h b/client/core/defs.h index 3a3ff565..61c45e4e 100644 --- a/client/core/defs.h +++ b/client/core/defs.h @@ -68,7 +68,10 @@ enum ErrorCode OpenSslFailed, OpenVpnExecutableCrashed, ShadowSocksExecutableCrashed, - CloakExecutableCrashed + CloakExecutableCrashed, + + // import and install errors + ImportInvalidConfigError }; } // namespace amnezia diff --git a/client/core/errorstrings.cpp b/client/core/errorstrings.cpp index 17b40b09..dd298c76 100644 --- a/client/core/errorstrings.cpp +++ b/client/core/errorstrings.cpp @@ -57,6 +57,8 @@ QString errorString(ErrorCode code){ case (OpenVpnTapAdapterError): return QObject::tr("Can't setup OpenVPN TAP network adapter"); case (AddressPoolError): return QObject::tr("VPN pool error: no available addresses"); + case (ImportInvalidConfigError): return QObject::tr("The config does not contain any containers and credentiaks for connecting to the server"); + case(InternalError): default: return QObject::tr("Internal error"); diff --git a/client/images/controls/file-cog-2.svg b/client/images/controls/file-cog-2.svg new file mode 100644 index 00000000..20815319 --- /dev/null +++ b/client/images/controls/file-cog-2.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/client/resources.qrc b/client/resources.qrc index db53165c..185c4469 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -249,5 +249,10 @@ images/controls/server.svg ui/qml/Pages2/PageSettingsServerProtocols.qml ui/qml/Pages2/PageSettingsServerServices.qml + ui/qml/Pages2/PageSetupWizardViewConfig.qml + images/controls/file-cog-2.svg + ui/qml/Components/QuestionDrawer.qml + ui/qml/Pages2/PageDeinstalling.qml + ui/qml/Controls2/BackButtonType.qml diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index 617fd5ee..c4717012 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -9,21 +9,7 @@ ConnectionController::ConnectionController(const QSharedPointer &s } -bool ConnectionController::onConnectionButtonClicked() -{ - if (!isConnected()) { - openVpnConnection(); - } else { - closeVpnConnection(); - } -} - -bool ConnectionController::isConnected() -{ - return m_isConnected; -} - -bool ConnectionController::openVpnConnection() +void ConnectionController::openConnection() { int serverIndex = m_serversModel->getDefaultServerIndex(); QModelIndex serverModelIndex = m_serversModel->index(serverIndex); @@ -35,16 +21,6 @@ bool ConnectionController::openVpnConnection() const QJsonObject &containerConfig = qvariant_cast(m_containersModel->data(containerModelIndex, ContainersModel::Roles::ConfigRole)); - //todo error handling - qApp->processEvents(); - emit connectToVpn(serverIndex, credentials, container, containerConfig); - m_isConnected = true; - - -// int serverIndex = m_settings->defaultServerIndex(); -// ServerCredentials credentials = m_settings->serverCredentials(serverIndex); -// DockerContainer container = m_settings->defaultContainer(serverIndex); - // if (m_settings->containers(serverIndex).isEmpty()) { // set_labelErrorText(tr("VPN Protocols is not installed.\n Please install VPN container at first")); // set_pushButtonConnectChecked(false); @@ -57,20 +33,23 @@ bool ConnectionController::openVpnConnection() // return; // } - -// const QJsonObject &containerConfig = m_settings->containerConfig(serverIndex, container); - -// set_labelErrorText(""); -// set_pushButtonConnectChecked(true); -// set_pushButtonConnectEnabled(false); - -// qApp->processEvents(); - -// emit connectToVpn(serverIndex, credentials, container, containerConfig); + //todo error handling + qApp->processEvents(); + emit connectToVpn(serverIndex, credentials, container, containerConfig); } -bool ConnectionController::closeVpnConnection() +void ConnectionController::closeConnection() { emit disconnectFromVpn(); - m_isConnected = false; +} + +bool ConnectionController::isConnected() +{ + return m_isConnected; +} + +void ConnectionController::setIsConnected(bool isConnected) +{ + m_isConnected = isConnected; + emit isConnectedChanged(); } diff --git a/client/ui/controllers/connectionController.h b/client/ui/controllers/connectionController.h index 8282a582..b1b0ff98 100644 --- a/client/ui/controllers/connectionController.h +++ b/client/ui/controllers/connectionController.h @@ -10,24 +10,26 @@ class ConnectionController : public QObject Q_OBJECT public: + Q_PROPERTY(bool isConnected READ isConnected WRITE setIsConnected NOTIFY isConnectedChanged) + explicit ConnectionController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, QObject *parent = nullptr); -public slots: - bool onConnectionButtonClicked(); - bool isConnected(); + void setIsConnected(bool isConnected); + +public slots: + void openConnection(); + void closeConnection(); signals: void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig); void disconnectFromVpn(); void connectionStateChanged(Vpn::ConnectionState state); + void isConnectedChanged(); private: - bool openVpnConnection(); - bool closeVpnConnection(); - QSharedPointer m_serversModel; QSharedPointer m_containersModel; diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index ea1e7ad6..48e811e0 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -1,6 +1,9 @@ #include "importController.h" #include +#include + +#include "core/errorstrings.h" namespace { enum class ConfigTypes { @@ -39,50 +42,68 @@ ImportController::ImportController(const QSharedPointer &serversMo } -bool ImportController::importFromFile(const QUrl &fileUrl) +void ImportController::extractConfigFromFile(const QUrl &fileUrl) { QFile file(fileUrl.toLocalFile()); if (file.open(QIODevice::ReadOnly)) { - QByteArray data = file.readAll(); + QString data = file.readAll(); auto configFormat = checkConfigFormat(data); if (configFormat == ConfigTypes::OpenVpn) { - return importOpenVpnConfig(data); + m_config = extractOpenVpnConfig(data); } else if (configFormat == ConfigTypes::WireGuard) { - return importWireGuardConfig(data); + m_config = extractWireGuardConfig(data); } else { - return importAmneziaConfig(data); + m_config = extractAmneziaConfig(data); } + + m_configFileName = QFileInfo(file.fileName()).fileName(); } - return false; } -bool ImportController::import(const QJsonObject &config) +void ImportController::extractConfigFromCode(QString code) +{ + m_config = extractAmneziaConfig(code); +} + +QString ImportController::getConfig() +{ + return QJsonDocument(m_config).toJson(QJsonDocument::Indented); +} + +QString ImportController::getConfigFileName() +{ + return m_configFileName; +} + +void ImportController::importConfig() { ServerCredentials credentials; - credentials.hostName = config.value(config_key::hostName).toString(); - credentials.port = config.value(config_key::port).toInt(); - credentials.userName = config.value(config_key::userName).toString(); - credentials.secretData = config.value(config_key::password).toString(); + credentials.hostName = m_config.value(config_key::hostName).toString(); + credentials.port = m_config.value(config_key::port).toInt(); + credentials.userName = m_config.value(config_key::userName).toString(); + credentials.secretData = m_config.value(config_key::password).toString(); - if (credentials.isValid() || config.contains(config_key::containers)) { - m_settings->addServer(config); + if (credentials.isValid() || m_config.contains(config_key::containers)) { + m_serversModel->addServer(m_config); - if (config.value(config_key::containers).toArray().isEmpty()) { - m_settings->setDefaultServer(m_settings->serversCount() - 1); + if (!m_config.value(config_key::containers).toArray().isEmpty()) { + auto newServerIndex = m_serversModel->index(m_serversModel->getServersCount() - 1); + m_serversModel->setData(newServerIndex, true, ServersModel::ServersModelRoles::IsDefaultRole); } emit importFinished(); } else { qDebug() << "Failed to import profile"; - qDebug().noquote() << QJsonDocument(config).toJson(); - return false; + qDebug().noquote() << QJsonDocument(m_config).toJson(); + emit importErrorOccurred(errorString(ErrorCode::ImportInvalidConfigError)); } - return true; + m_config = {}; + m_configFileName.clear(); } -bool ImportController::importAmneziaConfig(QString data) +QJsonObject ImportController::extractAmneziaConfig(QString &data) { data.replace("vpn://", ""); QByteArray ba = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); @@ -92,13 +113,7 @@ bool ImportController::importAmneziaConfig(QString data) ba = ba_uncompressed; } - QJsonObject config; - config = QJsonDocument::fromJson(ba).object(); - if (!config.isEmpty()) { - return import(config); - } - - return false; + return QJsonDocument::fromJson(ba).object(); } //bool ImportController::importConnectionFromQr(const QByteArray &data) @@ -116,7 +131,7 @@ bool ImportController::importAmneziaConfig(QString data) // return false; //} -bool ImportController::importOpenVpnConfig(const QString &data) +QJsonObject ImportController::extractOpenVpnConfig(const QString &data) { QJsonObject openVpnConfig; openVpnConfig[config_key::config] = data; @@ -156,10 +171,10 @@ bool ImportController::importOpenVpnConfig(const QString &data) config[config_key::hostName] = hostName; - return import(config); + return config; } -bool ImportController::importWireGuardConfig(const QString &data) +QJsonObject ImportController::extractWireGuardConfig(const QString &data) { QJsonObject lastConfig; lastConfig[config_key::config] = data; @@ -200,5 +215,5 @@ bool ImportController::importWireGuardConfig(const QString &data) config[config_key::hostName] = hostName; - return import(config); + return config; } diff --git a/client/ui/controllers/importController.h b/client/ui/controllers/importController.h index 7dd548d8..114bb531 100644 --- a/client/ui/controllers/importController.h +++ b/client/ui/controllers/importController.h @@ -18,20 +18,27 @@ public: QObject *parent = nullptr); public slots: - bool importFromFile(const QUrl &fileUrl); + void importConfig(); + void extractConfigFromFile(const QUrl &fileUrl); + void extractConfigFromCode(QString code); + QString getConfig(); + QString getConfigFileName(); signals: void importFinished(); + void importErrorOccurred(QString errorMessage); private: - bool import(const QJsonObject &config); - bool importAmneziaConfig(QString data); - bool importOpenVpnConfig(const QString &data); - bool importWireGuardConfig(const QString &data); + QJsonObject extractAmneziaConfig(QString &data); + QJsonObject extractOpenVpnConfig(const QString &data); + QJsonObject extractWireGuardConfig(const QString &data); QSharedPointer m_serversModel; QSharedPointer m_containersModel; std::shared_ptr m_settings; + QJsonObject m_config; + QString m_configFileName; + }; #endif // IMPORTCONTROLLER_H diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 1d041deb..22ee0bb3 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -9,14 +9,14 @@ namespace PageLoader { Q_NAMESPACE - enum class PageEnum { PageStart = 0, PageHome, PageShare, + enum class PageEnum { PageStart = 0, PageHome, PageShare, PageDeinstalling, PageSettingsServersList, PageSettings, PageSettingsServerData, PageSettingsServerInfo, PageSettingsServerProtocols, PageSettingsServerServices, PageSetupWizardStart, PageTest, PageSetupWizardCredentials, PageSetupWizardProtocols, PageSetupWizardEasy, PageSetupWizardProtocolSettings, PageSetupWizardInstalling, PageSetupWizardConfigSource, - PageSetupWizardTextKey + PageSetupWizardTextKey, PageSetupWizardViewConfig }; Q_ENUM_NS(PageEnum) diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml index 73accef4..06c148f1 100644 --- a/client/ui/qml/Components/ConnectButton.qml +++ b/client/ui/qml/Components/ConnectButton.qml @@ -13,7 +13,7 @@ Button { id: border source: connectionProccess.running ? "/images/connectionProgress.svg" : - ConnectionController.isConnected() ? "/images/connectionOff.svg" : "/images/connectionOn.svg" + ConnectionController.isConnected ? "/images/connectionOff.svg" : "/images/connectionOn.svg" RotationAnimator { id: connectionProccess @@ -46,7 +46,7 @@ Button { } onClicked: { - ConnectionController.onConnectionButtonClicked() + ConnectionController.isConnected ? ConnectionController.closeConnection() : ConnectionController.openConnection() } Connections { @@ -61,6 +61,7 @@ Button { console.log("Disconnected") connectionProccess.running = false root.text = qsTr("Connect") + ConnectionController.isConnected = false break } case ConnectionState.Preparing: { @@ -78,7 +79,8 @@ Button { case ConnectionState.Connected: { console.log("Connected") connectionProccess.running = false - root.text = qsTr("Connected") + root.text = qsTr("Disconnect") + ConnectionController.isConnected = true break } case ConnectionState.Disconnecting: { diff --git a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml index d80d5e5a..3c1ecd5b 100644 --- a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml +++ b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml @@ -14,20 +14,6 @@ DrawerType { width: parent.width height: parent.height * 0.4375 - background: Rectangle { - anchors.fill: parent - anchors.bottomMargin: -radius - radius: 16 - color: "#1C1D21" - - border.color: "#2C2D30" - border.width: 1 - } - - Overlay.modal: Rectangle { - color: Qt.rgba(14/255, 14/255, 17/255, 0.8) - } - ColumnLayout { anchors.top: parent.top anchors.left: parent.left diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index c3e98fbb..0d42b8e9 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -27,44 +27,54 @@ ListView { delegate: Item { implicitWidth: rootWidth - implicitHeight: containerRadioButton.implicitHeight + implicitHeight: content.implicitHeight - VerticalRadioButton { - id: containerRadioButton + ColumnLayout { + id: content anchors.fill: parent anchors.rightMargin: 16 anchors.leftMargin: 16 - text: name - descriptionText: description + VerticalRadioButton { + id: containerRadioButton - ButtonGroup.group: containersRadioButtonGroup + Layout.fillWidth: true - imageSource: "qrc:/images/controls/download.svg" - showImage: !isInstalled + text: name + descriptionText: description - checkable: isInstalled - checked: isDefault + ButtonGroup.group: containersRadioButtonGroup - onClicked: { - if (checked) { - isDefault = true - menuContent.currentIndex = index - containersDropDown.menuVisible = false - } else { - ContainersModel.setCurrentlyInstalledContainerIndex(proxyContainersModel.mapToSource(index)) - InstallController.setShouldCreateServer(false) - goToPage(PageEnum.PageSetupWizardProtocolSettings) - containersDropDown.menuVisible = false - menu.visible = false + imageSource: "qrc:/images/controls/download.svg" + showImage: !isInstalled + + checkable: isInstalled + checked: isDefault + + onClicked: { + if (checked) { + isDefault = true + menuContent.currentIndex = index + containersDropDown.menuVisible = false + } else { + ContainersModel.setCurrentlyInstalledContainerIndex(proxyContainersModel.mapToSource(index)) + InstallController.setShouldCreateServer(false) + goToPage(PageEnum.PageSetupWizardProtocolSettings) + containersDropDown.menuVisible = false + menu.visible = false + } + } + + MouseArea { + anchors.fill: containerRadioButton + cursorShape: Qt.PointingHandCursor + enabled: false } } - MouseArea { - anchors.fill: containerRadioButton - cursorShape: Qt.PointingHandCursor - enabled: false + DividerType { + Layout.fillWidth: true } } diff --git a/client/ui/qml/Components/QuestionDrawer.qml b/client/ui/qml/Components/QuestionDrawer.qml new file mode 100644 index 00000000..a79f9140 --- /dev/null +++ b/client/ui/qml/Components/QuestionDrawer.qml @@ -0,0 +1,77 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import "../Controls2" +import "../Controls2/TextTypes" + +DrawerType { + id: root + + property string headerText + property string descriptionText + property string yesButtonText + property string noButtonText + + property var yesButtonFunction + property var noButtonFunction + + width: parent.width + height: parent.height * 0.5 + + ColumnLayout { + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + spacing: 8 + + Header2TextType { + Layout.fillWidth: true + + text: headerText + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 8 + + text: descriptionText + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 16 + + text: yesButtonText + + onClicked: { + if (yesButtonFunction && typeof yesButtonFunction === "function") { + yesButtonFunction() + } + } + } + + BasicButtonType { + Layout.fillWidth: true + + 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: noButtonText + + onClicked: { + if (noButtonFunction && typeof noButtonFunction === "function") { + noButtonFunction() + } + } + } + } +} diff --git a/client/ui/qml/Controls2/BackButtonType.qml b/client/ui/qml/Controls2/BackButtonType.qml new file mode 100644 index 00000000..bec31823 --- /dev/null +++ b/client/ui/qml/Controls2/BackButtonType.qml @@ -0,0 +1,55 @@ +import QtQuick +import QtQuick.Layouts +import Qt5Compat.GraphicalEffects + +Item { + id: root + + property string backButtonImage: "qrc:/images/controls/arrow-left.svg" + property var backButtonFunction + + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight + + RowLayout { + id: content + + anchors.fill: parent + + ImageButtonType { + image: backButtonImage + imageColor: "#D7D8DB" + + onClicked: { + if (backButtonFunction && typeof backButtonFunction === "function") { + backButtonFunction() + } else { + closePage() + } + } + } + + Rectangle { + id: background + Layout.fillWidth: true + + color: "transparent" + + ShaderEffectSource { + id: effectSource + + sourceItem: background + anchors.fill: background + sourceRect: Qt.rect(x,y, width, height) + } + + FastBlur { + id: blur + anchors.fill: effectSource + + source: effectSource + radius: 100 + } + } + } +} diff --git a/client/ui/qml/Controls2/DrawerType.qml b/client/ui/qml/Controls2/DrawerType.qml index bede3700..e3c9d588 100644 --- a/client/ui/qml/Controls2/DrawerType.qml +++ b/client/ui/qml/Controls2/DrawerType.qml @@ -17,4 +17,18 @@ Drawer { velocity: 4 } } + + background: Rectangle { + anchors.fill: parent + anchors.bottomMargin: -radius + radius: 16 + color: "#1C1D21" + + border.color: "#2C2D30" + border.width: 1 + } + + Overlay.modal: Rectangle { + color: Qt.rgba(14/255, 14/255, 17/255, 0.8) + } } diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 5ac50fa7..700d9981 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -117,28 +117,9 @@ Item { width: parent.width height: parent.height * 0.9 - background: Rectangle { - anchors.fill: parent - anchors.bottomMargin: -radius - radius: 16 - color: "#1C1D21" - - border.color: "#494B50" - border.width: 1 - } - - Overlay.modal: Rectangle { - color: Qt.rgba(14/255, 14/255, 17/255, 0.8) - } - - Header2Type { + ColumnLayout { id: header - headerText: root.headerText - backButtonImage: root.headerBackButtonImage - - width: parent.width - anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right @@ -146,8 +127,11 @@ Item { anchors.leftMargin: 16 anchors.rightMargin: 16 - backButtonFunction: function() { - root.menuVisible = false + BackButtonType { + backButtonImage: root.headerBackButtonImage + backButtonFunction: function() { + root.menuVisible = false + } } } @@ -164,6 +148,17 @@ Item { spacing: 16 + Header2Type { + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + headerText: root.headerText + + width: parent.width + } + Loader { id: listViewLoader sourceComponent: root.listView diff --git a/client/ui/qml/Controls2/Header2Type.qml b/client/ui/qml/Controls2/Header2Type.qml index ce9d804a..9433f52a 100644 --- a/client/ui/qml/Controls2/Header2Type.qml +++ b/client/ui/qml/Controls2/Header2Type.qml @@ -6,10 +6,7 @@ import "TextTypes" Item { id: root - property string backButtonImage property string actionButtonImage - - property var backButtonFunction property var actionButtonFunction property string headerText @@ -22,25 +19,6 @@ Item { id: content anchors.fill: parent - ImageButtonType { - id: backButton - - Layout.leftMargin: -6 - - image: root.backButtonImage - imageColor: "#D7D8DB" - - visible: image ? true : false - - onClicked: { - if (backButtonFunction && typeof backButtonFunction === "function") { - backButtonFunction() - } else { - closePage() - } - } - } - RowLayout { Header2TextType { id: header diff --git a/client/ui/qml/Controls2/HeaderType.qml b/client/ui/qml/Controls2/HeaderType.qml index 2c0fbd85..19958205 100644 --- a/client/ui/qml/Controls2/HeaderType.qml +++ b/client/ui/qml/Controls2/HeaderType.qml @@ -6,10 +6,7 @@ import "TextTypes" Item { id: root - property string backButtonImage property string actionButtonImage - - property var backButtonFunction property var actionButtonFunction property string headerText @@ -22,25 +19,6 @@ Item { id: content anchors.fill: parent - ImageButtonType { - id: backButton - - Layout.leftMargin: -6 - - image: root.backButtonImage - imageColor: "#D7D8DB" - - visible: image ? true : false - - onClicked: { - if (backButtonFunction && typeof backButtonFunction === "function") { - backButtonFunction() - } else { - closePage() - } - } - } - RowLayout { Header1TextType { id: header diff --git a/client/ui/qml/Controls2/ProgressBarType.qml b/client/ui/qml/Controls2/ProgressBarType.qml index f2f2370a..183c3736 100644 --- a/client/ui/qml/Controls2/ProgressBarType.qml +++ b/client/ui/qml/Controls2/ProgressBarType.qml @@ -11,9 +11,11 @@ ProgressBar { color: "#412102" } - contentItem: Rectangle { - width: root.visualPosition * root.width - height: root.height - color: "#FBB26A" + contentItem: Item { + Rectangle { + width: root.visualPosition * parent.width + height: parent.height + color: "#FBB26A" + } } } diff --git a/client/ui/qml/Controls2/SwitcherType.qml b/client/ui/qml/Controls2/SwitcherType.qml index e4df04fa..b593ece8 100644 --- a/client/ui/qml/Controls2/SwitcherType.qml +++ b/client/ui/qml/Controls2/SwitcherType.qml @@ -59,7 +59,6 @@ Switch { } } - contentItem: ColumnLayout { contentItem: ColumnLayout { id: content diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml index 3458f741..b41f0b40 100644 --- a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -6,7 +6,6 @@ Item { id: root property string headerText - property string textFieldText property string textFieldPlaceholderText property bool textFieldEditable: true @@ -14,6 +13,7 @@ Item { property var clickedFunc property alias textField: textField + property alias textFieldText: textField.text implicitHeight: 74 @@ -53,7 +53,6 @@ Item { id: textField enabled: root.textFieldEditable - text: root.textFieldText color: "#d7d8db" placeholderText: textFieldPlaceholderText diff --git a/client/ui/qml/Pages2/PageDeinstalling.qml b/client/ui/qml/Pages2/PageDeinstalling.qml new file mode 100644 index 00000000..1dc542e0 --- /dev/null +++ b/client/ui/qml/Pages2/PageDeinstalling.qml @@ -0,0 +1,91 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +PageType { + id: root + + SortFilterProxyModel { + id: proxyServersModel + sourceModel: ServersModel + filters: [ + ValueFilter { + roleName: "isCurrentlyProcessed" + value: true + } + ] + } + + FlickableType { + id: fl + anchors.fill: parent + contentHeight: content.height + + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + spacing: 16 + + Repeater { + model: proxyServersModel + delegate: Item { + implicitWidth: parent.width + implicitHeight: delegateContent.implicitHeight + + ColumnLayout { + id: delegateContent + + anchors.fill: parent + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + HeaderType { + Layout.fillWidth: true + Layout.topMargin: 20 + + headerText: qsTr("Removing services from ") + name + } + + ProgressBarType { + id: progressBar + + Layout.fillWidth: true + Layout.topMargin: 32 + + Timer { + id: timer + + interval: 300 + repeat: true + running: true + onTriggered: { + progressBar.value += 0.001 + } + } + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 8 + + text: "Обычно это занимает не больше 5 минут" + } + } + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 0ecbafcc..4f3c4b6a 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -113,20 +113,6 @@ PageType { width: parent.width height: parent.height * 0.90 - background: Rectangle { - anchors.fill: parent - anchors.bottomMargin: -radius - radius: 16 - - color: "#1C1D21" - border.color: root.borderColor - border.width: 1 - } - - Overlay.modal: Rectangle { - color: Qt.rgba(14/255, 14/255, 17/255, 0.8) - } - ColumnLayout { id: serversMenuHeader anchors.top: parent.top diff --git a/client/ui/qml/Pages2/PageSettings.qml b/client/ui/qml/Pages2/PageSettings.qml index d3b87c19..300421b7 100644 --- a/client/ui/qml/Pages2/PageSettings.qml +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -71,7 +71,6 @@ PageType { iconImage: "qrc:/images/controls/app.svg" clickedFunction: function() { - goToPage(PageEnum.PageSetupWizardTextKey) } } @@ -85,7 +84,6 @@ PageType { iconImage: "qrc:/images/controls/save.svg" clickedFunction: function() { - goToPage(PageEnum.PageSetupWizardTextKey) } } @@ -99,7 +97,6 @@ PageType { iconImage: "qrc:/images/controls/amnezia.svg" clickedFunction: function() { - goToPage(PageEnum.PageSetupWizardTextKey) } } diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 3ab76299..c1f84cb0 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -9,6 +9,7 @@ import ProtocolEnum 1.0 import "../Controls2" import "../Controls2/TextTypes" +import "../Components" PageType { id: root @@ -33,7 +34,19 @@ PageType { descriptionText: "May be needed when changing other settings" clickedFunction: function() { - ContainersModel.clearCachedProfiles() + questionDrawer.headerText = qsTr("Clear cached profiles?") + questionDrawer.descriptionText = qsTr("some description") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + ContainersModel.clearCachedProfiles() + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true } } @@ -46,15 +59,27 @@ PageType { textColor: "#EB5757" clickedFunction: function() { - if (ServersModel.isDefaultServerCurrentlyProcessed && ConnectionController.isConnected()) { - ConnectionController.closeVpnConnection() + questionDrawer.headerText = qsTr("Remove server?") + questionDrawer.descriptionText = qsTr("All installed AmneziaVPN services will still remain on the server.") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + if (ServersModel.isDefaultServerCurrentlyProcessed && ConnectionController.isConnected) { + ConnectionController.closeConnection() + } + ServersModel.removeServer() + if (!ServersModel.getServersCount()) { + PageController.replaceStartPage() + } else { + goToStartPage() + } } - ServersModel.removeServer() - if (!ServersModel.getServersCount()) { - PageController.replaceStartPage() - } else { - goToStartPage() + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false } + questionDrawer.visible = true } } @@ -67,14 +92,32 @@ PageType { textColor: "#EB5757" clickedFunction: function() { - if (ServersModel.isDefaultServerCurrentlyProcessed && ConnectionController.isConnected()) { - ConnectionController.closeVpnConnection() + questionDrawer.headerText = qsTr("Clear server from Amnezia software?") + questionDrawer.descriptionText = qsTr(" All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted.") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + goToPage(PageEnum.PageDeinstalling) + if (ServersModel.isDefaultServerCurrentlyProcessed && ConnectionController.isConnected) { + ConnectionController.closeVpnConnection() + } + ContainersModel.removeAllContainers() + closePage() } - ContainersModel.removeAllContainers() + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true } } DividerType {} + + QuestionDrawer { + id: questionDrawer + } } } } diff --git a/client/ui/qml/Pages2/PageSettingsServerInfo.qml b/client/ui/qml/Pages2/PageSettingsServerInfo.qml index 29a0c3c1..5e422230 100644 --- a/client/ui/qml/Pages2/PageSettingsServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsServerInfo.qml @@ -30,8 +30,6 @@ PageType { } ColumnLayout { - id: content - anchors.fill: parent spacing: 16 @@ -40,24 +38,27 @@ PageType { id: header model: proxyServersModel - delegate: HeaderType { - Layout.fillWidth: true + delegate: ColumnLayout { + id: content + Layout.topMargin: 20 Layout.leftMargin: 16 Layout.rightMargin: 16 - actionButtonImage: "qrc:/images/controls/edit-3.svg" - backButtonImage: "qrc:/images/controls/arrow-left.svg" - - headerText: name - descriptionText: hostName - - actionButtonFunction: function() { - connectionTypeSelection.visible = true + BackButtonType { } - backButtonFunction: function() { - closePage() + HeaderType { + Layout.fillWidth: true + + actionButtonImage: "qrc:/images/controls/edit-3.svg" + + headerText: name + descriptionText: hostName + + actionButtonFunction: function() { + connectionTypeSelection.visible = true + } } } } @@ -74,23 +75,14 @@ PageType { TabButtonType { isSelected: tabBar.currentIndex === 0 text: qsTr("Protocols") -// onClicked: { -// tabBarStackView.goToTabBarPage(PageEnum.PageSettingsServerProtocols) -// } } TabButtonType { isSelected: tabBar.currentIndex === 1 text: qsTr("Services") -// onClicked: { -// tabBarStackView.goToTabBarPage(PageEnum.PageSettingsServerServices) -// } } TabButtonType { isSelected: tabBar.currentIndex === 2 text: qsTr("Data") -// onClicked: { -// tabBarStackView.goToTabBarPage(PageEnum.PageSettingsServerData) -// } } } @@ -110,25 +102,5 @@ PageType { stackView: root.stackView } } - -// StackViewType { -// id: tabBarStackView - -// Layout.preferredWidth: root.width -// Layout.preferredHeight: root.height - tabBar.implicitHeight - header.implicitHeight - -// function goToTabBarPage(page) { -// var pagePath = PageController.getPagePath(page) -// while (tabBarStackView.depth > 1) { -// tabBarStackView.pop() -// } -// tabBarStackView.replace(pagePath, { "objectName" : pagePath }) -// } - -// Component.onCompleted: { -// var pagePath = PageController.getPagePath(PageEnum.PageSettingsServerProtocols) -// tabBarStackView.push(pagePath, { "objectName" : pagePath }) -// } -// } } } diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index 63a40cc0..9c5ddc74 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -17,7 +17,7 @@ import "../Components" PageType { id: root - HeaderType { + ColumnLayout { id: header anchors.top: parent.top @@ -28,13 +28,19 @@ PageType { anchors.leftMargin: 16 anchors.rightMargin: 16 - actionButtonImage: "qrc:/images/controls/plus.svg" - backButtonImage: "qrc:/images/controls/arrow-left.svg" + BackButtonType { + } - headerText: "Серверы" + HeaderType { + Layout.fillWidth: true - actionButtonFunction: function() { - connectionTypeSelection.visible = true + actionButtonImage: "qrc:/images/controls/plus.svg" + + headerText: "Серверы" + + actionButtonFunction: function() { + connectionTypeSelection.visible = true + } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 25b88f76..79e596af 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -13,14 +13,6 @@ import "../Config" PageType { id: root - Connections { - target: ImportController - - function onImportFinished() { - - } - } - FlickableType { id: fl anchors.top: root.top @@ -34,13 +26,18 @@ PageType { anchors.left: parent.left anchors.right: parent.right + BackButtonType { + Layout.topMargin: 20 + Layout.rightMargin: 16 + Layout.leftMargin: 16 + } + HeaderType { Layout.fillWidth: true Layout.topMargin: 20 Layout.rightMargin: 16 Layout.leftMargin: 16 - backButtonImage: "qrc:/images/controls/arrow-left.svg" headerText: "Подключение к серверу" descriptionText: "Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватывать ваши данные.\n @@ -71,7 +68,8 @@ PageType { FileDialog { id: fileDialog onAccepted: { - ImportController.importFromFile(selectedFile) + ImportController.extractConfigFromFile(selectedFile) + goToPage(PageEnum.PageSetupWizardViewConfig) } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index 95463c3e..49197962 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -11,9 +11,20 @@ import "../Config" PageType { id: root + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + anchors.topMargin: 20 + } + FlickableType { id: fl - anchors.top: root.top + anchors.top: backButton.bottom anchors.bottom: root.bottom contentHeight: content.height @@ -30,9 +41,6 @@ PageType { HeaderType { Layout.fillWidth: true - Layout.topMargin: 20 - - backButtonImage: "qrc:/images/controls/arrow-left.svg" headerText: "Подключение к серверу" } diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index 8707c19b..99f6ccfb 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -30,11 +30,22 @@ PageType { } } + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + anchors.topMargin: 20 + } + FlickableType { id: fl - anchors.top: root.top + anchors.top: backButton.bottom anchors.bottom: root.bottom - contentHeight: content.implicitHeight + buttonContinue.anchors.bottomMargin + contentHeight: content.implicitHeight + continueButton.anchors.bottomMargin Column { id: content @@ -44,7 +55,6 @@ PageType { anchors.right: parent.right anchors.rightMargin: 16 anchors.leftMargin: 16 - anchors.topMargin: 20 spacing: 16 @@ -53,9 +63,7 @@ PageType { implicitWidth: parent.width - backButtonImage: "qrc:/images/controls/arrow-left.svg" - - headerText: qsTr("What is the level of Internet control in your region?") + headerText: qsTr("What is the level of internet control in your region?") } ListView { @@ -118,11 +126,10 @@ PageType { } BasicButtonType { - id: buttonContinue + id: continueButton implicitWidth: parent.width - anchors.topMargin: 24 - anchors.bottomMargin: 32 + anchors.bottomMargin: 24 text: qsTr("Continue") diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index cf7b1d28..c18c4d27 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -109,7 +109,7 @@ PageType { Layout.fillWidth: true Layout.topMargin: 32 - value: progressBarValue +// value: progressBarValue Timer { id: timer @@ -118,7 +118,7 @@ PageType { repeat: true running: true onTriggered: { - progressBarValue += 0.001 + progressBar.value += 0.001 } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index fa3ac838..39761cee 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -62,13 +62,14 @@ PageType { anchors.rightMargin: 16 anchors.leftMargin: 16 + BackButtonType { + Layout.topMargin: 20 + } + HeaderType { id: header Layout.fillWidth: true - Layout.topMargin: 20 - - backButtonImage: "qrc:/images/controls/arrow-left.svg" headerText: "Установка " + name descriptionText: "Эти настройки можно будет изменить позже" diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index cdacaf04..75b16afd 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -48,9 +48,12 @@ PageType { spacing: 16 + BackButtonType { + width: parent.width + } + HeaderType { width: parent.width - backButtonImage: "qrc:/images/controls/arrow-left.svg" headerText: "Протокол подключения" descriptionText: "Выберите более приоритетный для вас. Позже можно будет установить остальные протоколы и доп сервисы, вроде DNS-прокси и SFTP." diff --git a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml index 43dbda0e..f74c3733 100644 --- a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml +++ b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml @@ -29,23 +29,26 @@ PageType { spacing: 16 + BackButtonType { + Layout.topMargin: 20 + } + HeaderType { Layout.fillWidth: true - Layout.topMargin: 20 - backButtonImage: "qrc:/images/controls/arrow-left.svg" - - headerText: "Ключ для подключения" - descriptionText: "Строка, которая начинается с vpn://..." + headerText: qsTr("Connection key") + descriptionText: qsTr("A line that starts with vpn://...") } TextFieldWithHeaderType { + id: textKey + Layout.fillWidth: true Layout.topMargin: 32 - headerText: "Ключ" + headerText: qsTr("Key") textFieldPlaceholderText: "vpn://" - buttonText: "Вставить" + buttonText: qsTr("Insert") clickedFunc: function() { textField.text = "" @@ -63,10 +66,11 @@ PageType { anchors.leftMargin: 16 anchors.bottomMargin: 32 - text: qsTr("Подключиться") + text: qsTr("Continue") onClicked: function() { -// goToPage(PageEnum.PageSetupWizardInstalling) + ImportController.extractConfigFromCode(textKey.textFieldText) + goToPage(PageEnum.PageSetupWizardViewConfig) } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml new file mode 100644 index 00000000..ca679e79 --- /dev/null +++ b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml @@ -0,0 +1,152 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +PageType { + id: root + + property bool showContent: false + + Connections { + target: ImportController + + function onImportErrorOccurred(errorMessage) { + closePage() + PageController.showErrorMessage(errorMessage) + } + + function onImportFinished() { + goToStartPage() + if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) { + PageController.restorePageHomeState() + } else if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSettings)) { + goToPage(PageEnum.PageSettingsServersList, false) + } else { + var pagePath = PageController.getPagePath(PageEnum.PageStart) + stackView.replace(pagePath, { "objectName" : pagePath }) + } + } + } + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + anchors.topMargin: 20 + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: root.bottom + contentHeight: content.implicitHeight + connectButton.implicitHeight + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + HeaderType { + headerText: qsTr("New connection") + } + + RowLayout { + Layout.topMargin: 32 + spacing: 8 + + visible: fileName.text !== "" + + Image { + source: "qrc:/images/controls/file-cog-2.svg" + } + + Header2TextType { + id: fileName + + Layout.fillWidth: true + + text: ImportController.getConfigFileName() + } + } + + CaptionTextType { + Layout.fillWidth: true + Layout.topMargin: 16 + + text: qsTr("Do not use connection code from public sources. It could be created to intercept your data.") + color: "#878B91" + } + + BasicButtonType { + 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.bottomMargin: 16 + + implicitHeight: configContent.implicitHeight + + radius: 10 + color: "#1C1D21" + + visible: showContent + + ParagraphTextType { + id: configContent + + anchors.fill: parent + anchors.margins: 16 + + text: ImportController.getConfig() + } + } + } + } + + ColumnLayout { + id: connectButton + + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + BasicButtonType { + Layout.fillWidth: true + Layout.bottomMargin: 32 + + text: qsTr("Connect") + onClicked: { + ImportController.importConfig() + } + } + } +} diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 44577037..436194b2 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -46,6 +46,8 @@ PageType { Component.onCompleted: { var pagePath = PageController.getPagePath(PageEnum.PageHome) tabBarStackView.push(pagePath, { "objectName" : pagePath }) + ServersModel.setCurrentlyProcessedServerIndex(ServersModel.getDefaultServerIndex()) + ContainersModel.setCurrentlyProcessedServerIndex(ServersModel.getDefaultServerIndex()) } } diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index bda1c709..9bb6aa28 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -36,7 +36,7 @@ Window { focus: true Component.onCompleted: { - var pagePath = PageController.getPagePath(PageEnum.PageStart) + var pagePath = PageController.getInitialPage() rootStackView.push(pagePath, { "objectName" : pagePath }) } } From 80fca589af50d626ea8ea864a73b2b57c493a455 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 5 Jun 2023 22:40:35 +0800 Subject: [PATCH 026/278] added ConnectionController error handling --- client/amnezia_application.cpp | 95 ++++--------------- client/amnezia_application.h | 4 +- .../ui/controllers/connectionController.cpp | 45 ++++++--- client/ui/controllers/connectionController.h | 8 ++ client/ui/controllers/importController.cpp | 44 ++++----- client/ui/controllers/installController.cpp | 1 - client/ui/models/containers_model.cpp | 14 +-- client/ui/qml/Components/ConnectButton.qml | 53 ++++++++--- client/ui/qml/Pages2/PageHome.qml | 2 +- .../PageSetupWizardProtocolSettings.qml | 7 +- client/ui/qml/main2.qml | 1 - 11 files changed, 134 insertions(+), 140 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index c8bdcf3b..dbeaac38 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -4,36 +4,15 @@ #include #include #include +#include -#include "core/servercontroller.h" #include "logger.h" #include "defines.h" -#include #include "platforms/ios/QRCodeReaderBase.h" #include "ui/pages.h" -#include "ui/pages_logic/AppSettingsLogic.h" -#include "ui/pages_logic/GeneralSettingsLogic.h" -#include "ui/pages_logic/NetworkSettingsLogic.h" -#include "ui/pages_logic/NewServerProtocolsLogic.h" -#include "ui/pages_logic/QrDecoderLogic.h" -#include "ui/pages_logic/ServerConfiguringProgressLogic.h" -#include "ui/pages_logic/ServerContainersLogic.h" -#include "ui/pages_logic/ServerListLogic.h" -#include "ui/pages_logic/ServerSettingsLogic.h" -#include "ui/pages_logic/ServerContainersLogic.h" -#include "ui/pages_logic/ShareConnectionLogic.h" -#include "ui/pages_logic/SitesLogic.h" -#include "ui/pages_logic/StartPageLogic.h" -#include "ui/pages_logic/VpnLogic.h" -#include "ui/pages_logic/WizardLogic.h" - -#include "ui/pages_logic/protocols/CloakLogic.h" -#include "ui/pages_logic/protocols/OpenVpnLogic.h" -#include "ui/pages_logic/protocols/ShadowSocksLogic.h" - #if defined(Q_OS_IOS) #include "platforms/ios/QtAppDelegate-C-Interface.h" #endif @@ -76,10 +55,6 @@ AmneziaApplication::~AmneziaApplication() QObject::disconnect(m_engine, 0,0,0); delete m_engine; } - if (m_uiLogic) { - QObject::disconnect(m_uiLogic, 0,0,0); - delete m_uiLogic; - } if (m_protocolProps) delete m_protocolProps; if (m_containerProps) delete m_containerProps; @@ -88,7 +63,6 @@ AmneziaApplication::~AmneziaApplication() void AmneziaApplication::init() { m_engine = new QQmlApplicationEngine; - m_uiLogic = new UiLogic(m_settings, m_configurator); const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml")); QObject::connect(m_engine, &QQmlApplicationEngine::objectCreated, @@ -108,14 +82,8 @@ void AmneziaApplication::init() m_vpnConnection.reset(new VpnConnection(m_settings, m_configurator)); - m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel)); + m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel, m_vpnConnection)); - connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, - m_connectionController.get(), &ConnectionController::connectionStateChanged); - connect(m_connectionController.get(), &ConnectionController::connectToVpn, - m_vpnConnection.get(), &VpnConnection::connectToVpn, Qt::QueuedConnection); - connect(m_connectionController.get(), &ConnectionController::disconnectFromVpn, - m_vpnConnection.get(), &VpnConnection::disconnectFromVpn, Qt::QueuedConnection); m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get()); m_pageController.reset(new PageController(m_serversModel)); @@ -128,17 +96,12 @@ void AmneziaApplication::init() m_engine->rootContext()->setContextProperty("ImportController", m_importController.get()); // - m_uiLogic->registerPagesLogic(); - -#if defined(Q_OS_IOS) - setStartPageLogic(m_uiLogic->pageLogic()); -#endif m_engine->load(url); - if (m_engine->rootObjects().size() > 0) { - m_uiLogic->setQmlRoot(m_engine->rootObjects().at(0)); - } +// if (m_engine->rootObjects().size() > 0) { +// m_uiLogic->setQmlRoot(m_engine->rootObjects().at(0)); +// } if (m_settings->isSaveLogs()) { if (!Logger::init()) { @@ -146,23 +109,23 @@ void AmneziaApplication::init() } } -#ifdef Q_OS_WIN - if (m_parser.isSet("a")) m_uiLogic->showOnStartup(); - else emit m_uiLogic->show(); -#else - m_uiLogic->showOnStartup(); -#endif +//#ifdef Q_OS_WIN +// if (m_parser.isSet("a")) m_uiLogic->showOnStartup(); +// else emit m_uiLogic->show(); +//#else +// m_uiLogic->showOnStartup(); +//#endif - // TODO - fix -#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) - if (isPrimary()) { - QObject::connect(this, &SingleApplication::instanceStarted, m_uiLogic, [this](){ - qDebug() << "Secondary instance started, showing this window instead"; - emit m_uiLogic->show(); - emit m_uiLogic->raise(); - }); - } -#endif +// // TODO - fix +//#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) +// if (isPrimary()) { +// QObject::connect(this, &SingleApplication::instanceStarted, m_uiLogic, [this](){ +// qDebug() << "Secondary instance started, showing this window instead"; +// emit m_uiLogic->show(); +// emit m_uiLogic->raise(); +// }); +// } +//#endif } @@ -174,16 +137,10 @@ void AmneziaApplication::registerTypes() qRegisterMetaType("TransportProto"); qRegisterMetaType("Proto"); qRegisterMetaType("ServiceType"); - qRegisterMetaType("Page"); - qRegisterMetaType("PageProtocolLogicBase *"); - - -// declareQmlPageEnum(); declareQmlProtocolEnum(); declareQmlContainerEnum(); - qmlRegisterType("PageType", 1, 0, "PageType"); qmlRegisterType("QRCodeReader", 1, 0, "QRCodeReader"); m_containerProps = new ContainerProps; @@ -201,16 +158,6 @@ void AmneziaApplication::loadFonts() { QQuickStyle::setStyle("Basic"); - QFontDatabase::addApplicationFont(":/fonts/Lato-Black.ttf"); - QFontDatabase::addApplicationFont(":/fonts/Lato-BlackItalic.ttf"); - QFontDatabase::addApplicationFont(":/fonts/Lato-Bold.ttf"); - QFontDatabase::addApplicationFont(":/fonts/Lato-BoldItalic.ttf"); - QFontDatabase::addApplicationFont(":/fonts/Lato-Italic.ttf"); - QFontDatabase::addApplicationFont(":/fonts/Lato-Light.ttf"); - QFontDatabase::addApplicationFont(":/fonts/Lato-LightItalic.ttf"); - QFontDatabase::addApplicationFont(":/fonts/Lato-Regular.ttf"); - QFontDatabase::addApplicationFont(":/fonts/Lato-Thin.ttf"); - QFontDatabase::addApplicationFont(":/fonts/Lato-ThinItalic.ttf"); QFontDatabase::addApplicationFont(":/fonts/pt-root-ui_vf.ttf"); } diff --git a/client/amnezia_application.h b/client/amnezia_application.h index f4156d15..0fd6842c 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -11,7 +11,6 @@ #include "settings.h" #include "vpnconnection.h" -#include "ui/uilogic.h" #include "configurators/vpn_configurator.h" #include "ui/models/servers_model.h" @@ -54,7 +53,6 @@ public: private: QQmlApplicationEngine *m_engine {}; - UiLogic *m_uiLogic {}; std::shared_ptr m_settings; std::shared_ptr m_configurator; @@ -67,7 +65,7 @@ private: QSharedPointer m_containersModel; QSharedPointer m_serversModel; - QScopedPointer m_vpnConnection; + QSharedPointer m_vpnConnection; QScopedPointer m_connectionController; QScopedPointer m_pageController; diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index c4717012..fbbb67d9 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -2,11 +2,31 @@ #include +#include "core/errorstrings.h" + ConnectionController::ConnectionController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, - QObject *parent) : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel) + const QSharedPointer &vpnConnection, + QObject *parent) + : QObject(parent) + , m_serversModel(serversModel) + , m_containersModel(containersModel) + , m_vpnConnection(vpnConnection) { - + connect(m_vpnConnection.get(), + &VpnConnection::connectionStateChanged, + this, + &ConnectionController::connectionStateChanged); + connect(this, + &ConnectionController::connectToVpn, + m_vpnConnection.get(), + &VpnConnection::connectToVpn, + Qt::QueuedConnection); + connect(this, + &ConnectionController::disconnectFromVpn, + m_vpnConnection.get(), + &VpnConnection::disconnectFromVpn, + Qt::QueuedConnection); } void ConnectionController::openConnection() @@ -21,19 +41,11 @@ void ConnectionController::openConnection() const QJsonObject &containerConfig = qvariant_cast(m_containersModel->data(containerModelIndex, ContainersModel::Roles::ConfigRole)); -// if (m_settings->containers(serverIndex).isEmpty()) { -// set_labelErrorText(tr("VPN Protocols is not installed.\n Please install VPN container at first")); -// set_pushButtonConnectChecked(false); -// return; -// } + if (container == DockerContainer::None) { + emit connectionErrorOccurred(tr("VPN Protocols is not installed.\n Please install VPN container at first")); + return; + } -// if (container == DockerContainer::None) { -// set_labelErrorText(tr("VPN Protocol not chosen")); -// set_pushButtonConnectChecked(false); -// return; -// } - - //todo error handling qApp->processEvents(); emit connectToVpn(serverIndex, credentials, container, containerConfig); } @@ -43,6 +55,11 @@ void ConnectionController::closeConnection() emit disconnectFromVpn(); } +QString ConnectionController::getLastConnectionError() +{ + return errorString(m_vpnConnection->lastError()); +} + bool ConnectionController::isConnected() { return m_isConnected; diff --git a/client/ui/controllers/connectionController.h b/client/ui/controllers/connectionController.h index b1b0ff98..93ee28a7 100644 --- a/client/ui/controllers/connectionController.h +++ b/client/ui/controllers/connectionController.h @@ -4,6 +4,7 @@ #include "ui/models/servers_model.h" #include "ui/models/containers_model.h" #include "protocols/vpnprotocol.h" +#include "vpnconnection.h" class ConnectionController : public QObject { @@ -14,6 +15,7 @@ public: explicit ConnectionController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, + const QSharedPointer &vpnConnection, QObject *parent = nullptr); bool isConnected(); @@ -23,16 +25,22 @@ public slots: void openConnection(); void closeConnection(); + QString getLastConnectionError(); + signals: void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig); void disconnectFromVpn(); void connectionStateChanged(Vpn::ConnectionState state); void isConnectedChanged(); + void connectionErrorOccurred(QString errorMessage); + private: QSharedPointer m_serversModel; QSharedPointer m_containersModel; + QSharedPointer m_vpnConnection; + bool m_isConnected = false; }; diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 48e811e0..2724f9cd 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -6,33 +6,32 @@ #include "core/errorstrings.h" namespace { - enum class ConfigTypes { - Amnezia, - OpenVpn, - WireGuard - }; +enum class ConfigTypes { Amnezia, OpenVpn, WireGuard }; - ConfigTypes checkConfigFormat(const QString &config) - { - const QString openVpnConfigPatternCli = "client"; - const QString openVpnConfigPatternProto1 = "proto tcp"; - const QString openVpnConfigPatternProto2 = "proto udp"; - const QString openVpnConfigPatternDriver1 = "dev tun"; - const QString openVpnConfigPatternDriver2 = "dev tap"; +ConfigTypes checkConfigFormat(const QString &config) +{ + const QString openVpnConfigPatternCli = "client"; + const QString openVpnConfigPatternProto1 = "proto tcp"; + const QString openVpnConfigPatternProto2 = "proto udp"; + const QString openVpnConfigPatternDriver1 = "dev tun"; + const QString openVpnConfigPatternDriver2 = "dev tap"; - const QString wireguardConfigPatternSectionInterface = "[Interface]"; - const QString wireguardConfigPatternSectionPeer = "[Peer]"; + const QString wireguardConfigPatternSectionInterface = "[Interface]"; + const QString wireguardConfigPatternSectionPeer = "[Peer]"; - if (config.contains(openVpnConfigPatternCli) && - (config.contains(openVpnConfigPatternProto1) || config.contains(openVpnConfigPatternProto2)) && - (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) { - return ConfigTypes::OpenVpn; - } else if (config.contains(wireguardConfigPatternSectionInterface) && config.contains(wireguardConfigPatternSectionPeer)) { - return ConfigTypes::WireGuard; - } - return ConfigTypes::Amnezia; + if (config.contains(openVpnConfigPatternCli) + && (config.contains(openVpnConfigPatternProto1) + || config.contains(openVpnConfigPatternProto2)) + && (config.contains(openVpnConfigPatternDriver1) + || config.contains(openVpnConfigPatternDriver2))) { + return ConfigTypes::OpenVpn; + } else if (config.contains(wireguardConfigPatternSectionInterface) + && config.contains(wireguardConfigPatternSectionPeer)) { + return ConfigTypes::WireGuard; } + return ConfigTypes::Amnezia; } +} // namespace ImportController::ImportController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, @@ -64,6 +63,7 @@ void ImportController::extractConfigFromFile(const QUrl &fileUrl) void ImportController::extractConfigFromCode(QString code) { m_config = extractAmneziaConfig(code); + m_configFileName = ""; } QString ImportController::getConfig() diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 15a24c31..2fe96962 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -10,7 +10,6 @@ InstallController::InstallController(const QSharedPointer &servers const std::shared_ptr &settings, QObject *parent) : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings) { - } void InstallController::install(DockerContainer container, int port, TransportProto transportProto) diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index b240878d..e65e79d7 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -22,17 +22,19 @@ bool ContainersModel::setData(const QModelIndex &index, const QVariant &value, i switch (role) { case NameRole: -// return ContainerProps::containerHumanNames().value(container); + // return ContainerProps::containerHumanNames().value(container); case DescRole: -// return ContainerProps::containerDescriptions().value(container); + // return ContainerProps::containerDescriptions().value(container); case ConfigRole: - m_settings->setContainerConfig(m_currentlyProcessedServerIndex, container, value.toJsonObject()); + m_settings->setContainerConfig(m_currentlyProcessedServerIndex, + container, + value.toJsonObject()); case ServiceTypeRole: -// return ContainerProps::containerService(container); + // return ContainerProps::containerService(container); case DockerContainerRole: -// return container; + // return container; case IsInstalledRole: -// return m_settings->containers(m_currentlyProcessedServerIndex).contains(container); + // return m_settings->containers(m_currentlyProcessedServerIndex).contains(container); case IsDefaultRole: m_settings->setDefaultContainer(m_currentlyProcessedServerIndex, container); emit defaultContainerChanged(); diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml index 06c148f1..3c34a6b0 100644 --- a/client/ui/qml/Components/ConnectButton.qml +++ b/client/ui/qml/Components/ConnectButton.qml @@ -7,27 +7,48 @@ import ConnectionState 1.0 Button { id: root + Connections { + target: ConnectionController + + function onConnectionErrorOccurred(errorMessage) { + PageController.showErrorMessage(errorMessage) + } + } + text: qsTr("Connect") - background: Image { - id: border + background: Item { + clip: true - source: connectionProccess.running ? "/images/connectionProgress.svg" : - ConnectionController.isConnected ? "/images/connectionOff.svg" : "/images/connectionOn.svg" + implicitHeight: border.implicitHeight + implicitWidth: border.implicitWidth - RotationAnimator { - id: connectionProccess + Image { + id: border - target: border - running: false - from: 0 - to: 360 - loops: Animation.Infinite - duration: 1250 + source: connectionProccess.running ? "/images/connectionProgress.svg" : + ConnectionController.isConnected ? "/images/connectionOff.svg" : "/images/connectionOn.svg" + RotationAnimator { + id: connectionProccess + + target: border + running: false + from: 0 + to: 360 + loops: Animation.Infinite + duration: 1250 + } + + Behavior on source { + PropertyAnimation { duration: 200 } + } } - Behavior on source { - PropertyAnimation { duration: 200 } + MouseArea { + anchors.fill: parent + + cursorShape: Qt.PointingHandCursor + enabled: false } } @@ -46,7 +67,7 @@ Button { } onClicked: { - ConnectionController.isConnected ? ConnectionController.closeConnection() : ConnectionController.openConnection() + connectionProccess.running ? ConnectionController.closeConnection() : ConnectionController.openConnection() } Connections { @@ -98,6 +119,8 @@ Button { case ConnectionState.Error: { console.log("Error") connectionProccess.running = false + root.text = qsTr("Connect") + PageController.showErrorMessage(ConnectionController.getLastConnectionError()) break } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 4f3c4b6a..e5521787 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -262,7 +262,7 @@ PageType { Layout.fillWidth: true text: name - descriptionText: "description" + descriptionText: hostName checked: index === serversMenuContent.currentIndex diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 39761cee..19f81d09 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -39,8 +39,6 @@ PageType { anchors.left: parent.left anchors.right: parent.right - spacing: 16 - ListView { // todo change id naming id: containers @@ -63,6 +61,8 @@ PageType { anchors.leftMargin: 16 BackButtonType { + id: backButton + Layout.topMargin: 20 } @@ -136,6 +136,7 @@ PageType { id: port Layout.fillWidth: true + Layout.topMargin: 16 headerText: "Port" } @@ -143,7 +144,7 @@ PageType { // todo make it dynamic implicitHeight: root.height - port.implicitHeight - transportProtoBackground.implicitHeight - transportProtoHeader.implicitHeight - - header.implicitHeight - installButton.implicitHeight - 100 + header.implicitHeight - backButton.implicitHeight - installButton.implicitHeight - 116 color: "transparent" } diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index 9bb6aa28..44a85925 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -3,7 +3,6 @@ import QtQuick.Window import QtQuick.Controls import QtQuick.Layouts -import PageType 1.0 import PageEnum 1.0 import "Config" From 68d9394d9fb3d0bbb3d46953714303bbfc565569 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 5 Jun 2023 17:49:20 +0300 Subject: [PATCH 027/278] fixed windows build errors after refactoring --- client/configurators/ssh_configurator.cpp | 6 ++-- .../protocols/ikev2_vpn_protocol_windows.cpp | 30 +++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/client/configurators/ssh_configurator.cpp b/client/configurators/ssh_configurator.cpp index e1435bc3..d94e4468 100644 --- a/client/configurators/ssh_configurator.cpp +++ b/client/configurators/ssh_configurator.cpp @@ -69,14 +69,14 @@ void SshConfigurator::openSshTerminal(const ServerCredentials &credentials) p->setProcessEnvironment(prepareEnv()); p->setProgram(qApp->applicationDirPath() + "\\cygwin\\putty.exe"); - if (credentials.password.contains("PRIVATE KEY")) { + if (credentials.secretData.contains("PRIVATE KEY")) { // todo: connect by key // p->setNativeArguments(QString("%1@%2") -// .arg(credentials.userName).arg(credentials.hostName).arg(credentials.password)); +// .arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData)); } else { p->setNativeArguments(QString("%1@%2 -pw %3") - .arg(credentials.userName).arg(credentials.hostName).arg(credentials.password)); + .arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData)); } #else p->setProgram("/bin/bash"); diff --git a/client/protocols/ikev2_vpn_protocol_windows.cpp b/client/protocols/ikev2_vpn_protocol_windows.cpp index 7564f969..5c471e22 100644 --- a/client/protocols/ikev2_vpn_protocol_windows.cpp +++ b/client/protocols/ikev2_vpn_protocol_windows.cpp @@ -35,14 +35,14 @@ Ikev2Protocol::~Ikev2Protocol() void Ikev2Protocol::stop() { - setConnectionState(VpnProtocol::Disconnecting); + setConnectionState(Vpn::ConnectionState::Disconnecting); { if (! disconnect_vpn() ){ qDebug()<<"We don't disconnect"; - setConnectionState(VpnProtocol::Error); + setConnectionState(Vpn::ConnectionState::Error); } else { - setConnectionState(VpnProtocol::Disconnected); + setConnectionState(Vpn::ConnectionState::Disconnected); } } } @@ -55,40 +55,40 @@ void Ikev2Protocol::newConnectionStateEventReceived(UINT unMsg, tagRASCONNSTATE { case RASCS_OpenPort: //qDebug()<<__FUNCTION__ << __LINE__; - setConnectionState(Preparing); + setConnectionState(Vpn::ConnectionState::Preparing); break; case RASCS_PortOpened: //qDebug()<<__FUNCTION__ << __LINE__; - setConnectionState(Preparing); + setConnectionState(Vpn::ConnectionState::Preparing); break; case RASCS_ConnectDevice: //qDebug()<<__FUNCTION__ << __LINE__; - setConnectionState(Preparing); + setConnectionState(Vpn::ConnectionState::Preparing); break; case RASCS_DeviceConnected: //qDebug()<<__FUNCTION__ << __LINE__; - setConnectionState(Preparing); + setConnectionState(Vpn::ConnectionState::Preparing); break; case RASCS_AllDevicesConnected: //qDebug()<<__FUNCTION__ << __LINE__; - setConnectionState(Preparing); + setConnectionState(Vpn::ConnectionState::Preparing); break; case RASCS_Authenticate: //qDebug()<<__FUNCTION__ << __LINE__; - setConnectionState(Preparing); + setConnectionState(Vpn::ConnectionState::Preparing); break; case RASCS_AuthNotify: //qDebug()<<__FUNCTION__ << __LINE__; if (dwError != 0) { //qDebug() << "have error" << dwError; - setConnectionState(Disconnected); + setConnectionState(Vpn::ConnectionState::Disconnected); } else { //qDebug() << "RASCS_AuthNotify but no error" << dwError; } break; case RASCS_AuthRetry: //qDebug()<<__FUNCTION__ << __LINE__; - setConnectionState(Preparing); + setConnectionState(Vpn::ConnectionState::Preparing); break; case RASCS_AuthCallback: qDebug()<<__FUNCTION__ << __LINE__; @@ -151,16 +151,16 @@ void Ikev2Protocol::newConnectionStateEventReceived(UINT unMsg, tagRASCONNSTATE qDebug()<<__FUNCTION__ << __LINE__; break; case RASCS_PasswordExpired: - setConnectionState(Error); + setConnectionState(Vpn::ConnectionState::Error); qDebug()<<__FUNCTION__ << __LINE__; break; case RASCS_Connected: // = RASCS_DONE: - setConnectionState(Connected); + setConnectionState(Vpn::ConnectionState::Connected); //qDebug()<<__FUNCTION__ << __LINE__; break; case RASCS_Disconnected: - setConnectionState(Disconnected); + setConnectionState(Vpn::ConnectionState::Disconnected); //qDebug()<<__FUNCTION__ << __LINE__; break; default: @@ -177,7 +177,7 @@ void Ikev2Protocol::readIkev2Configuration(const QJsonObject &configuration) ErrorCode Ikev2Protocol::start() { QByteArray cert = QByteArray::fromBase64(m_config[config_key::cert].toString().toUtf8()); - setConnectionState(Connecting); + setConnectionState(Vpn::ConnectionState::Connecting); QTemporaryFile certFile; certFile.setAutoRemove(false); From c3f39ad24d7f617c846fb5a818fa0b8b1a34f10d Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 7 Jun 2023 13:17:48 +0300 Subject: [PATCH 028/278] added caching of servers and containers in models --- client/ui/models/containers_model.cpp | 52 +++++++++++-------- client/ui/models/containers_model.h | 5 ++ client/ui/models/servers_model.cpp | 27 ++++++---- client/ui/models/servers_model.h | 3 ++ .../Components/SettingsContainersListView.qml | 2 - client/ui/qml/Controls2/BackButtonType.qml | 16 ------ client/ui/qml/Pages2/PageHome.qml | 30 +++++------ client/ui/qml/Pages2/PageStart.qml | 4 +- 8 files changed, 69 insertions(+), 70 deletions(-) diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index e65e79d7..b6574439 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -21,23 +21,25 @@ bool ContainersModel::setData(const QModelIndex &index, const QVariant &value, i DockerContainer container = ContainerProps::allContainers().at(index.row()); switch (role) { - case NameRole: - // return ContainerProps::containerHumanNames().value(container); - case DescRole: - // return ContainerProps::containerDescriptions().value(container); - case ConfigRole: - m_settings->setContainerConfig(m_currentlyProcessedServerIndex, - container, - value.toJsonObject()); - case ServiceTypeRole: - // return ContainerProps::containerService(container); - case DockerContainerRole: - // return container; - case IsInstalledRole: - // return m_settings->containers(m_currentlyProcessedServerIndex).contains(container); - case IsDefaultRole: - m_settings->setDefaultContainer(m_currentlyProcessedServerIndex, container); - emit defaultContainerChanged(); + case NameRole: + // return ContainerProps::containerHumanNames().value(container); + case DescRole: + // return ContainerProps::containerDescriptions().value(container); + case ConfigRole: + m_settings->setContainerConfig(m_currentlyProcessedServerIndex, + container, + value.toJsonObject()); + case ServiceTypeRole: + // return ContainerProps::containerService(container); + case DockerContainerRole: + // return container; + case IsInstalledRole: + // return m_settings->containers(m_currentlyProcessedServerIndex).contains(container); + case IsDefaultRole: { + m_settings->setDefaultContainer(m_currentlyProcessedServerIndex, container); + m_defaultContainerIndex = container; + emit defaultContainerChanged(); + } } emit dataChanged(index, index); @@ -58,8 +60,10 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const return ContainerProps::containerHumanNames().value(container); case DescRole: return ContainerProps::containerDescriptions().value(container); - case ConfigRole: - return m_settings->containerConfig(m_currentlyProcessedServerIndex, container); + case ConfigRole: { + if (container == DockerContainer::None) return QJsonObject(); + return m_containers.value(container); + } case ServiceTypeRole: return ContainerProps::containerService(container); case DockerContainerRole: @@ -71,11 +75,11 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const case EasySetupDescriptionRole: return ContainerProps::easySetupDescription(container); case IsInstalledRole: - return m_settings->containers(m_currentlyProcessedServerIndex).contains(container); + return m_containers.contains(container); case IsCurrentlyInstalledRole: return container == static_cast(m_currentlyInstalledContainerIndex); case IsDefaultRole: - return container == m_settings->defaultContainer(m_currentlyProcessedServerIndex); + return container == m_defaultContainerIndex; case IsSupportedRole: return ContainerProps::isSupportedByCurrentPlatform(container); } @@ -87,6 +91,8 @@ void ContainersModel::setCurrentlyProcessedServerIndex(int index) { beginResetModel(); m_currentlyProcessedServerIndex = index; + m_containers = m_settings->containers(m_currentlyProcessedServerIndex); + m_defaultContainerIndex = m_settings->defaultContainer(m_currentlyProcessedServerIndex); endResetModel(); emit defaultContainerChanged(); } @@ -98,12 +104,12 @@ void ContainersModel::setCurrentlyInstalledContainerIndex(int index) DockerContainer ContainersModel::getDefaultContainer() { - return m_settings->defaultContainer(m_currentlyProcessedServerIndex); + return m_defaultContainerIndex; } QString ContainersModel::getDefaultContainerName() { - return ContainerProps::containerHumanNames().value(getDefaultContainer()); + return ContainerProps::containerHumanNames().value(m_defaultContainerIndex); } int ContainersModel::getCurrentlyInstalledContainerIndex() diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index 3ce7bd6b..5753a1dd 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -55,8 +55,13 @@ protected: QHash roleNames() const override; private: + QMap m_containers; + + int m_currentlyProcessedServerIndex; int m_currentlyInstalledContainerIndex; + DockerContainer m_defaultContainerIndex; + std::shared_ptr m_settings; }; diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index d05bb695..bb08e88c 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -2,24 +2,28 @@ ServersModel::ServersModel(std::shared_ptr settings, QObject *parent) : m_settings(settings), QAbstractListModel(parent) { - + m_servers = m_settings->serversArray(); + m_defaultServerIndex = m_settings->defaultServerIndex(); } int ServersModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); - return static_cast(m_settings->serversCount()); + return static_cast(m_servers.size()); } bool ServersModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid() || index.row() < 0 - || index.row() >= static_cast(m_settings->serversCount())) { + || index.row() >= static_cast(m_servers.size())) { return false; } switch (role) { - case IsDefaultRole: m_settings->setDefaultServer(index.row()); + case IsDefaultRole: { + m_settings->setDefaultServer(index.row()); + m_defaultServerIndex = m_settings->defaultServerIndex(); + } default: return true; } @@ -29,12 +33,11 @@ bool ServersModel::setData(const QModelIndex &index, const QVariant &value, int QVariant ServersModel::data(const QModelIndex &index, int role) const { - if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(m_settings->serversCount())) { + if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(m_servers.size())) { return QVariant(); } - const QJsonArray &servers = m_settings->serversArray(); - const QJsonObject server = servers.at(index.row()).toObject(); + const QJsonObject server = m_servers.at(index.row()).toObject(); switch (role) { case NameRole: { @@ -49,7 +52,7 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const case CredentialsRole: return QVariant::fromValue(m_settings->serverCredentials(index.row())); case IsDefaultRole: - return index.row() == m_settings->defaultServerIndex(); + return index.row() == m_defaultServerIndex; case IsCurrentlyProcessedRole: return index.row() == m_currenlyProcessedServerIndex; } @@ -59,12 +62,12 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const const int ServersModel::getDefaultServerIndex() { - return m_settings->defaultServerIndex(); + return m_defaultServerIndex; } const int ServersModel::getServersCount() { - return m_settings->serversCount(); + return m_servers.count(); } void ServersModel::setCurrentlyProcessedServerIndex(int index) @@ -74,7 +77,7 @@ void ServersModel::setCurrentlyProcessedServerIndex(int index) bool ServersModel::isDefaultServerCurrentlyProcessed() { - return m_settings->defaultServerIndex() == m_currenlyProcessedServerIndex; + return m_defaultServerIndex == m_currenlyProcessedServerIndex; } ServerCredentials ServersModel::getCurrentlyProcessedServerCredentials() @@ -86,6 +89,7 @@ void ServersModel::addServer(const QJsonObject &server) { beginResetModel(); m_settings->addServer(server); + m_servers = m_settings->serversArray(); endResetModel(); } @@ -93,6 +97,7 @@ void ServersModel::removeServer() { beginResetModel(); m_settings->removeServer(m_currenlyProcessedServerIndex); + m_servers = m_settings->serversArray(); if (m_settings->defaultServerIndex() == m_currenlyProcessedServerIndex) { m_settings->setDefaultServer(0); diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index 54ac5ef4..593babc3 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -46,8 +46,11 @@ protected: QHash roleNames() const override; private: + QJsonArray m_servers; + std::shared_ptr m_settings; + int m_defaultServerIndex; int m_currenlyProcessedServerIndex; }; diff --git a/client/ui/qml/Components/SettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml index 5175fd49..abc50837 100644 --- a/client/ui/qml/Components/SettingsContainersListView.qml +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -37,8 +37,6 @@ ListView { ButtonGroup.group: containersRadioButtonGroup - checked: isDefault - indicator: Rectangle { anchors.fill: parent color: containerRadioButton.hovered ? Qt.rgba(1, 1, 1, 0.08) : "transparent" diff --git a/client/ui/qml/Controls2/BackButtonType.qml b/client/ui/qml/Controls2/BackButtonType.qml index bec31823..389191e8 100644 --- a/client/ui/qml/Controls2/BackButtonType.qml +++ b/client/ui/qml/Controls2/BackButtonType.qml @@ -34,22 +34,6 @@ Item { Layout.fillWidth: true color: "transparent" - - ShaderEffectSource { - id: effectSource - - sourceItem: background - anchors.fill: background - sourceRect: Qt.rect(x,y, width, height) - } - - FastBlur { - id: blur - anchors.fill: effectSource - - source: effectSource - radius: 100 - } } } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index e5521787..227870c5 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -137,21 +137,6 @@ PageType { Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter spacing: 8 - SortFilterProxyModel { - id: proxyContainersModel - sourceModel: ContainersModel - filters: [ - ValueFilter { - roleName: "serviceType" - value: ProtocolEnum.Vpn - }, - ValueFilter { - roleName: "isSupported" - value: true - } - ] - } - DropDownType { id: containersDropDown @@ -176,7 +161,20 @@ PageType { listView: HomeContainersListView { rootWidth: root.width - model: proxyContainersModel + model: SortFilterProxyModel { + id: proxyContainersModel + sourceModel: ContainersModel + filters: [ + ValueFilter { + roleName: "serviceType" + value: ProtocolEnum.Vpn + }, + ValueFilter { + roleName: "isSupported" + value: true + } + ] + } currentIndex: ContainersModel.getDefaultContainer() } } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 436194b2..83b641a1 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -45,9 +45,9 @@ PageType { Component.onCompleted: { var pagePath = PageController.getPagePath(PageEnum.PageHome) - tabBarStackView.push(pagePath, { "objectName" : pagePath }) ServersModel.setCurrentlyProcessedServerIndex(ServersModel.getDefaultServerIndex()) ContainersModel.setCurrentlyProcessedServerIndex(ServersModel.getDefaultServerIndex()) + tabBarStackView.push(pagePath, { "objectName" : pagePath }) } } @@ -71,8 +71,8 @@ PageType { isSelected: tabBar.currentIndex === 0 image: "qrc:/images/controls/home.svg" onClicked: { - tabBarStackView.goToTabBarPage(PageEnum.PageHome) ContainersModel.setCurrentlyProcessedServerIndex(ServersModel.getDefaultServerIndex()) + tabBarStackView.goToTabBarPage(PageEnum.PageHome) } } TabImageButtonType { From 1fd48a1cf827236ec2fcc7b98db6b44469b33042 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 7 Jun 2023 18:28:32 +0800 Subject: [PATCH 029/278] added protocol settings page and openvpn settings page --- client/resources.qrc | 3 + client/ui/controllers/pageController.h | 28 +++++-- .../qml/Components/HomeContainersListView.qml | 1 + .../Components/Protocols/OpenVpnSettings.qml | 81 +++++++++++++++++++ .../Components/SettingsContainersListView.qml | 4 +- .../qml/Components/TransportProtoSelector.qml | 59 ++++++++++++++ .../qml/Controls2/HorizontalRadioButton.qml | 10 +-- client/ui/qml/Pages2/PageHome.qml | 5 +- .../qml/Pages2/PageSettingsServerProtocol.qml | 24 ++++++ .../ui/qml/Pages2/PageSettingsServersList.qml | 1 + .../PageSetupWizardProtocolSettings.qml | 59 +++----------- client/ui/qml/Pages2/PageStart.qml | 1 + 12 files changed, 209 insertions(+), 67 deletions(-) create mode 100644 client/ui/qml/Components/Protocols/OpenVpnSettings.qml create mode 100644 client/ui/qml/Components/TransportProtoSelector.qml create mode 100644 client/ui/qml/Pages2/PageSettingsServerProtocol.qml diff --git a/client/resources.qrc b/client/resources.qrc index 185c4469..e2f16853 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -254,5 +254,8 @@ ui/qml/Components/QuestionDrawer.qml ui/qml/Pages2/PageDeinstalling.qml ui/qml/Controls2/BackButtonType.qml + ui/qml/Pages2/PageSettingsServerProtocol.qml + ui/qml/Components/Protocols/OpenVpnSettings.qml + ui/qml/Components/TransportProtoSelector.qml diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 22ee0bb3..946c1ce5 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -9,14 +9,30 @@ namespace PageLoader { Q_NAMESPACE - enum class PageEnum { PageStart = 0, PageHome, PageShare, PageDeinstalling, + enum class PageEnum { + PageStart = 0, + PageHome, + PageShare, + PageDeinstalling, - PageSettingsServersList, PageSettings, PageSettingsServerData, PageSettingsServerInfo, - PageSettingsServerProtocols, PageSettingsServerServices, + PageSettingsServersList, + PageSettings, + PageSettingsServerData, + PageSettingsServerInfo, + PageSettingsServerProtocols, + PageSettingsServerServices, + PageSettingsServerProtocol, - PageSetupWizardStart, PageTest, PageSetupWizardCredentials, PageSetupWizardProtocols, PageSetupWizardEasy, - PageSetupWizardProtocolSettings, PageSetupWizardInstalling, PageSetupWizardConfigSource, - PageSetupWizardTextKey, PageSetupWizardViewConfig + PageSetupWizardStart, + PageTest, + PageSetupWizardCredentials, + PageSetupWizardProtocols, + PageSetupWizardEasy, + PageSetupWizardProtocolSettings, + PageSetupWizardInstalling, + PageSetupWizardConfigSource, + PageSetupWizardTextKey, + PageSetupWizardViewConfig }; Q_ENUM_NS(PageEnum) diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index 0d42b8e9..926302c4 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -20,6 +20,7 @@ ListView { height: menuContent.contentItem.height clip: true + interactive: false ButtonGroup { id: containersRadioButtonGroup diff --git a/client/ui/qml/Components/Protocols/OpenVpnSettings.qml b/client/ui/qml/Components/Protocols/OpenVpnSettings.qml new file mode 100644 index 00000000..8c036fc0 --- /dev/null +++ b/client/ui/qml/Components/Protocols/OpenVpnSettings.qml @@ -0,0 +1,81 @@ +import QtQuick 2.15 +import QtQuick.Controls +import QtQuick.Layouts + +import "../../Controls2" +import "../../Controls2/TextTypes" +import "../../Components" + +Item { + id: root + + ColumnLayout { + anchors.fill: parent + + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + spacing: 16 + + Header2TextType { + Layout.fillWidth: true + + text: "OpenVpn" + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + + headerText: qsTr("VPN Addresses Subnet") + } + + ParagraphTextType { + Layout.fillWidth: true + + text: qsTr("Network protocol") + } + + TransportProtoSelector { + Layout.fillWidth: true + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + + headerText: qsTr("Port") + } + + SwitcherType { + Layout.fillWidth: true + text: qsTr("Auto-negotiate encryption") + } + + DropDownType { + Layout.fillWidth: true + + } + + DropDownType { + Layout.fillWidth: true + + } + + CheckBoxType { + Layout.fillWidth: true + + text: qsTr("TLS auth") + } + + CheckBoxType { + Layout.fillWidth: true + + text: qsTr("Block DNS requests outside of VPN") + } + + SwitcherType { + Layout.fillWidth: true + + text: qsTr("Additional configuration commands") + } + } +} diff --git a/client/ui/qml/Components/SettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml index 5175fd49..334696b0 100644 --- a/client/ui/qml/Components/SettingsContainersListView.qml +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -18,6 +18,7 @@ ListView { height: root.contentItem.height clip: true + interactive: false ButtonGroup { id: containersRadioButtonGroup @@ -89,8 +90,7 @@ ListView { onClicked: { if (isInstalled) { -// isDefault = true -// root.currentIndex = index + goToPage(PageEnum.PageSettingsServerProtocol) } else { ContainersModel.setCurrentlyInstalledContainerIndex(root.model.mapToSource(index)) InstallController.setShouldCreateServer(false) diff --git a/client/ui/qml/Components/TransportProtoSelector.qml b/client/ui/qml/Components/TransportProtoSelector.qml new file mode 100644 index 00000000..6f5aa44b --- /dev/null +++ b/client/ui/qml/Components/TransportProtoSelector.qml @@ -0,0 +1,59 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import "../Controls2" +import "../Controls2/TextTypes" + +Rectangle { + id: root + + property var rootWidth: root.width + property int currentIndex + + property alias mouseArea: transportProtoButtonMouseArea + + implicitWidth: transportProtoButtonGroup.implicitWidth + implicitHeight: transportProtoButtonGroup.implicitHeight + + color: "#1C1D21" + radius: 16 + + RowLayout { + id: transportProtoButtonGroup + + spacing: 0 + + HorizontalRadioButton { + checked: root.currentIndex === 0 + + implicitWidth: (rootWidth - 32) / 2 + text: "UDP" + + hoverEnabled: !transportProtoButtonMouseArea.enabled + + onClicked: { + root.currentIndex = 0 + } + } + + HorizontalRadioButton { + checked: root.currentIndex === 1 + + implicitWidth: (rootWidth - 32) / 2 + text: "TCP" + + hoverEnabled: !transportProtoButtonMouseArea.enabled + + onClicked: { + root.currentIndex = 1 + } + } + } + + MouseArea { + id: transportProtoButtonMouseArea + + anchors.fill: parent + } +} diff --git a/client/ui/qml/Controls2/HorizontalRadioButton.qml b/client/ui/qml/Controls2/HorizontalRadioButton.qml index 6f00d210..86218b3f 100644 --- a/client/ui/qml/Controls2/HorizontalRadioButton.qml +++ b/client/ui/qml/Controls2/HorizontalRadioButton.qml @@ -2,6 +2,8 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import "TextTypes" + RadioButton { id: root @@ -74,15 +76,9 @@ RadioButton { anchors.fill: parent spacing: 16 - Text { + ButtonTextType { text: root.text - wrapMode: Text.WordWrap - color: "#D7D8DB" - font.pixelSize: 16 - font.weight: 400 - font.family: "PT Root UI VF" - height: 24 Layout.fillWidth: true Layout.rightMargin: 16 Layout.leftMargin: 16 diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index e5521787..276764a3 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -164,7 +164,7 @@ PageType { text: root.currentContainerName textColor: "#0E0E11" - headerText: "Протокол подключения" + headerText: qsTr("Протокол подключения") headerBackButtonImage: "qrc:/images/controls/arrow-left.svg" onRootButtonClicked: function() { @@ -198,7 +198,7 @@ PageType { actionButtonImage: "qrc:/images/controls/plus.svg" - headerText: "Серверы" + headerText: qsTr("Servers") actionButtonFunction: function() { menu.visible = false @@ -237,6 +237,7 @@ PageType { currentIndex: ServersModel.getDefaultServerIndex() clip: true + interactive: false delegate: Item { id: menuContentDelegate diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml new file mode 100644 index 00000000..800041f0 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -0,0 +1,24 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 +import ContainerProps 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" +import "../Components/Protocols" + +PageType { + id: root + + OpenVpnSettings { + anchors.fill: parent + } +} diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index 9c5ddc74..fa4ce86c 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -67,6 +67,7 @@ PageType { model: ServersModel clip: true + interactive: false delegate: Item { implicitWidth: servers.width diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 19f81d09..57fa497d 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -12,6 +12,7 @@ import "./" import "../Controls2" import "../Controls2/TextTypes" import "../Config" +import "../Components" PageType { id: root @@ -83,53 +84,11 @@ PageType { text: "Network protocol" } - Rectangle { - id: transportProtoBackground + TransportProtoSelector { + id: transportProtoSelector - implicitWidth: transportProtoButtonGroup.implicitWidth - implicitHeight: transportProtoButtonGroup.implicitHeight - - color: "#1C1D21" - radius: 16 - - RowLayout { - id: transportProtoButtonGroup - - property int currentIndex - spacing: 0 - - HorizontalRadioButton { - checked: transportProtoButtonGroup.currentIndex === 0 - - implicitWidth: (root.width - 32) / 2 - text: "UDP" - - hoverEnabled: !transportProtoButtonMouseArea.enabled - - onClicked: { - transportProtoButtonGroup.currentIndex = 0 - } - } - - HorizontalRadioButton { - checked: transportProtoButtonGroup.currentIndex === 1 - - implicitWidth: (root.width - 32) / 2 - text: "TCP" - - hoverEnabled: !transportProtoButtonMouseArea.enabled - - onClicked: { - transportProtoButtonGroup.currentIndex = 1 - } - } - } - - MouseArea { - id: transportProtoButtonMouseArea - - anchors.fill: parent - } + Layout.fillWidth: true + rootWidth: root.width } TextFieldWithHeaderType { @@ -143,7 +102,7 @@ PageType { Rectangle { // todo make it dynamic implicitHeight: root.height - port.implicitHeight - - transportProtoBackground.implicitHeight - transportProtoHeader.implicitHeight - + transportProtoSelector.implicitHeight - transportProtoHeader.implicitHeight - header.implicitHeight - backButton.implicitHeight - installButton.implicitHeight - 116 color: "transparent" @@ -159,7 +118,7 @@ PageType { onClicked: function() { goToPage(PageEnum.PageSetupWizardInstalling); - InstallController.install(dockerContainer, port.textFieldText, transportProtoButtonGroup.currentIndex) + InstallController.install(dockerContainer, port.textFieldText, transportProtoSelector.currentIndex) } } @@ -172,10 +131,10 @@ PageType { } else { port.textFieldText = ProtocolProps.defaultPort(defaultContainerProto) } - transportProtoButtonGroup.currentIndex = ProtocolProps.defaultTransportProto(defaultContainerProto) + transportProtoSelector.currentIndex = ProtocolProps.defaultTransportProto(defaultContainerProto) port.enabled = ProtocolProps.defaultPortChangeable(defaultContainerProto) - transportProtoButtonMouseArea.enabled = !ProtocolProps.defaultTransportProtoChangeable(defaultContainerProto) + transportProtoSelector.mouseArea.enabled = !ProtocolProps.defaultTransportProtoChangeable(defaultContainerProto) } } } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 436194b2..a801bb66 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -78,6 +78,7 @@ PageType { TabImageButtonType { isSelected: tabBar.currentIndex === 1 image: "qrc:/images/controls/share-2.svg" + onClicked: {} } TabImageButtonType { isSelected: tabBar.currentIndex === 2 From cd3263db5059e164119bb5c97556a675305758a4 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sat, 10 Jun 2023 05:25:41 +0300 Subject: [PATCH 030/278] made libssh::ssh_connect a non-blocking feature - extended error handling when connecting via ssh --- client/core/defs.h | 2 +- client/core/errorstrings.cpp | 1 + client/core/sshclient.cpp | 49 +++++++++++++------ client/core/sshclient.h | 2 +- client/ui/controllers/connectionController.h | 3 +- client/ui/qml/Controls2/BasicButtonType.qml | 4 +- client/ui/qml/Controls2/PopupType.qml | 12 +++-- .../ui/qml/Pages2/PageSettingsServerData.qml | 1 + client/ui/qml/main2.qml | 8 ++- client/vpnconnection.cpp | 6 +-- 10 files changed, 54 insertions(+), 34 deletions(-) diff --git a/client/core/defs.h b/client/core/defs.h index 61c45e4e..1c268246 100644 --- a/client/core/defs.h +++ b/client/core/defs.h @@ -36,7 +36,7 @@ enum ErrorCode // Ssh connection errors SshRequsetDeniedError, SshInterruptedError, SshInternalError, - SshPrivateKeyError, SshPrivateKeyFormatError, + SshPrivateKeyError, SshPrivateKeyFormatError, SshTimeoutError, // Ssh sftp errors SshSftpEofError, SshSftpNoSuchFileError, SshSftpPermissionDeniedError, diff --git a/client/core/errorstrings.cpp b/client/core/errorstrings.cpp index dd298c76..cd66186d 100644 --- a/client/core/errorstrings.cpp +++ b/client/core/errorstrings.cpp @@ -24,6 +24,7 @@ QString errorString(ErrorCode code){ case(SshInternalError): return QObject::tr("Ssh internal error"); case(SshPrivateKeyError): return QObject::tr("Invalid private key or invalid passphrase entered"); case(SshPrivateKeyFormatError): return QObject::tr("The selected private key format is not supported, use openssh ED25519 key types or PEM key types"); + case(SshTimeoutError): return QObject::tr("Timeout connecting to server"); // Libssh sftp errors case(SshSftpEofError): return QObject::tr("Sftp error: End-of-file encountered"); diff --git a/client/core/sshclient.cpp b/client/core/sshclient.cpp index 0367dc1f..e8d021ad 100644 --- a/client/core/sshclient.cpp +++ b/client/core/sshclient.cpp @@ -10,6 +10,8 @@ const uint32_t S_IRWXU = 0644; #endif namespace libssh { + const QString libsshTimeoutError = "Timeout connecting to"; + std::function Client::m_passphraseCallback; Client::Client(QObject *parent) : QObject(parent) @@ -45,11 +47,20 @@ namespace libssh { ssh_options_set(m_session, SSH_OPTIONS_USER, hostUsername.c_str()); ssh_options_set(m_session, SSH_OPTIONS_LOG_VERBOSITY, &logVerbosity); - int connectionResult = ssh_connect(m_session); + QFutureWatcher watcher; + QFuture future = QtConcurrent::run([this]() { + return ssh_connect(m_session); + }); + + QEventLoop wait; + connect(&watcher, &QFutureWatcher::finished, &wait, &QEventLoop::quit); + watcher.setFuture(future); + wait.exec(); + + int connectionResult = watcher.result(); if (connectionResult != SSH_OK) { - qDebug() << ssh_get_error(m_session); - return fromLibsshErrorCode(ssh_get_error_code(m_session)); + return fromLibsshErrorCode(); } std::string authUsername = credentials.userName.toStdString(); @@ -78,8 +89,8 @@ namespace libssh { ssh_key_free(privateKey); } if (authResult != SSH_OK) { - qDebug() << ssh_get_error(m_session); - ErrorCode errorCode = fromLibsshErrorCode(ssh_get_error_code(m_session)); + qCritical() << ssh_get_error(m_session); + ErrorCode errorCode = fromLibsshErrorCode(); if (errorCode == ErrorCode::NoError) { errorCode = ErrorCode::SshPrivateKeyFormatError; } @@ -88,8 +99,7 @@ namespace libssh { } else { authResult = ssh_userauth_password(m_session, authUsername.c_str(), credentials.secretData.toStdString().c_str()); if (authResult != SSH_OK) { - qDebug() << ssh_get_error(m_session); - return fromLibsshErrorCode(ssh_get_error_code(m_session)); + return fromLibsshErrorCode(); } } } @@ -188,16 +198,15 @@ namespace libssh { ErrorCode Client::writeResponse(const QString &data) { if (m_channel == nullptr) { - qDebug() << "ssh channel not initialized"; - return fromLibsshErrorCode(ssh_get_error_code(m_session)); + qCritical() << "ssh channel not initialized"; + return fromLibsshErrorCode(); } int bytesWritten = ssh_channel_write(m_channel, data.toUtf8(), (uint32_t)data.size()); if (bytesWritten == data.size() && ssh_channel_write(m_channel, "\n", 1)) { - return fromLibsshErrorCode(ssh_get_error_code(m_session)); + return fromLibsshErrorCode(); } - qDebug() << ssh_get_error(m_session); - return fromLibsshErrorCode(ssh_get_error_code(m_session)); + return fromLibsshErrorCode(); } ErrorCode Client::closeChannel() @@ -212,8 +221,7 @@ namespace libssh { ssh_channel_free(m_channel); m_channel = nullptr; } - qDebug() << ssh_get_error(m_session); - return fromLibsshErrorCode(ssh_get_error_code(m_session)); + return fromLibsshErrorCode(); } ErrorCode Client::sftpFileCopy(const SftpOverwriteMode overwriteMode, const std::string& localPath, const std::string& remotePath, const std::string& fileDesc) @@ -312,12 +320,21 @@ namespace libssh { sftp_free(m_sftpSession); m_sftpSession = nullptr; } - qDebug() << ssh_get_error(m_session); + qCritical() << ssh_get_error(m_session); return errorCode; } - ErrorCode Client::fromLibsshErrorCode(int errorCode) + ErrorCode Client::fromLibsshErrorCode() { + int errorCode = ssh_get_error_code(m_session); + if (errorCode != SSH_NO_ERROR) { + QString errorMessage = ssh_get_error(m_session); + qCritical() << errorMessage; + if (errorMessage.contains(libsshTimeoutError)) { + return ErrorCode::SshTimeoutError; + } + } + switch (errorCode) { case(SSH_NO_ERROR): return ErrorCode::NoError; case(SSH_REQUEST_DENIED): return ErrorCode::SshRequsetDeniedError; diff --git a/client/core/sshclient.h b/client/core/sshclient.h index db22d0dd..4e08faaa 100644 --- a/client/core/sshclient.h +++ b/client/core/sshclient.h @@ -40,7 +40,7 @@ namespace libssh { private: ErrorCode closeChannel(); ErrorCode closeSftpSession(); - ErrorCode fromLibsshErrorCode(int errorCode); + ErrorCode fromLibsshErrorCode(); ErrorCode fromLibsshSftpErrorCode(int errorCode); static int callback(const char *prompt, char *buf, size_t len, int echo, int verify, void *userdata); diff --git a/client/ui/controllers/connectionController.h b/client/ui/controllers/connectionController.h index 93ee28a7..c1e81ea3 100644 --- a/client/ui/controllers/connectionController.h +++ b/client/ui/controllers/connectionController.h @@ -19,13 +19,14 @@ public: QObject *parent = nullptr); bool isConnected(); - void setIsConnected(bool isConnected); + void setIsConnected(bool isConnected); //todo take state from vpnconnection? public slots: void openConnection(); void closeConnection(); QString getLastConnectionError(); + Vpn::ConnectionState connectionState(){return {};}; //todo update ConnectButton text on page change signals: void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig); diff --git a/client/ui/qml/Controls2/BasicButtonType.qml b/client/ui/qml/Controls2/BasicButtonType.qml index 266beefe..05074fa9 100644 --- a/client/ui/qml/Controls2/BasicButtonType.qml +++ b/client/ui/qml/Controls2/BasicButtonType.qml @@ -24,10 +24,10 @@ Button { radius: 16 color: { if (root.enabled) { - if(root.pressed) { + if (root.pressed) { return pressedColor } - return hovered ? hoveredColor : defaultColor + return root.hovered ? hoveredColor : defaultColor } else { return disabledColor } diff --git a/client/ui/qml/Controls2/PopupType.qml b/client/ui/qml/Controls2/PopupType.qml index dd92d5fe..61bdfd18 100644 --- a/client/ui/qml/Controls2/PopupType.qml +++ b/client/ui/qml/Controls2/PopupType.qml @@ -27,15 +27,17 @@ Popup { background: Rectangle { anchors.fill: parent - color: Qt.rgba(215/255, 216/255, 219/255, 0.95) + color: "white"//Qt.rgba(215/255, 216/255, 219/255, 0.95) radius: 4 } contentItem: RowLayout { - width: parent.width + anchors.fill: parent + anchors.leftMargin: 16 + anchors.rightMargin: 16 CaptionTextType { - horizontalAlignment: Text.AlignHCenter + horizontalAlignment: Text.AlignLeft Layout.fillWidth: true text: root.popupErrorMessageText @@ -44,7 +46,7 @@ Popup { BasicButtonType { visible: closeButtonVisible - defaultColor: Qt.rgba(215/255, 216/255, 219/255, 0.95) + defaultColor: "white"//"transparent"//Qt.rgba(215/255, 216/255, 219/255, 0.95) hoveredColor: "#C1C2C5" pressedColor: "#AEB0B7" disabledColor: "#494B50" @@ -52,7 +54,7 @@ Popup { textColor: "#0E0E11" borderWidth: 0 - text: "Close" + text: qsTr("Close") onClicked: { root.close() } diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index c1f84cb0..fe09ed01 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -74,6 +74,7 @@ PageType { PageController.replaceStartPage() } else { goToStartPage() + goToPage(PageEnum.PageSettingsServersList) } } questionDrawer.noButtonFunction = function() { diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index 44a85925..0be8e368 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -16,6 +16,9 @@ Window { minimumWidth: GC.isDesktop() ? 360 : 0 minimumHeight: GC.isDesktop() ? 640 : 0 + color: "#0E0E11" + + // todo onClosing: function() { console.debug("QML onClosing signal") UiLogic.onCloseWindow() @@ -23,11 +26,6 @@ Window { title: "AmneziaVPN" - Rectangle { - anchors.fill: parent - color: "#0E0E11" - } - StackViewType { id: rootStackView diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index 468a6d96..1f0902fb 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -336,7 +336,7 @@ void VpnConnection::connectToVpn(int serverIndex, ErrorCode e = ErrorCode::NoError; - m_vpnConfiguration = createVpnConfiguration(serverIndex, credentials, container, containerConfig); + m_vpnConfiguration = createVpnConfiguration(serverIndex, credentials, container, containerConfig, &e); if (e) { emit connectionStateChanged(Vpn::ConnectionState::Error); return; @@ -345,7 +345,7 @@ void VpnConnection::connectToVpn(int serverIndex, #if !defined (Q_OS_ANDROID) && !defined (Q_OS_IOS) m_vpnProtocol.reset(VpnProtocol::factory(container, m_vpnConfiguration)); if (!m_vpnProtocol) { - emit Vpn::ConnectionState::Error; + emit connectionStateChanged(Vpn::ConnectionState::Error); return; } m_vpnProtocol->prepare(); @@ -371,7 +371,7 @@ void VpnConnection::connectToVpn(int serverIndex, createProtocolConnections(); e = m_vpnProtocol.data()->start(); - if (e) emit Vpn::ConnectionState::Error; + if (e) emit connectionStateChanged(Vpn::ConnectionState::Error); } void VpnConnection::createProtocolConnections() { From be7386f0d76645b97e44b775c6b90b3cc740ab5f Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 13 Jun 2023 20:03:20 +0900 Subject: [PATCH 031/278] added exportController and PageShare - added a blank PageSettingsProtocol --- client/amnezia_application.cpp | 7 +- client/amnezia_application.h | 11 +- client/images/controls/check_off.png | Bin 17930 -> 0 bytes client/images/controls/check_on.png | Bin 18032 -> 0 bytes .../radio-button-inner-circle-pressed.png | Bin 0 -> 5125 bytes .../controls/radio-button-inner-circle.png | Bin 0 -> 7720 bytes .../images/controls/radio-button-pressed.svg | 3 + client/images/controls/radio-button.svg | 3 + client/images/controls/radio_off.png | Bin 491 -> 0 bytes client/images/controls/radio_on.png | Bin 624 -> 0 bytes client/resources.qrc | 10 +- client/ui/controllers/exportController.cpp | 156 +++++++++ client/ui/controllers/exportController.h | 44 +++ client/ui/controllers/importController.h | 1 + client/ui/controllers/installController.cpp | 3 +- client/ui/controllers/pageController.h | 1 - .../protocolSettingsController.cpp | 19 + .../controllers/protocolSettingsController.h | 31 ++ client/ui/models/containers_model.cpp | 22 +- client/ui/models/containers_model.h | 8 +- client/ui/models/servers_model.cpp | 5 + client/ui/models/servers_model.h | 1 + .../qml/Components/HomeContainersListView.qml | 2 +- .../Components/Protocols/OpenVpnSettings.qml | 70 ++++ .../Components/SettingsContainersListView.qml | 3 +- .../qml/Components/ShareConnectionDrawer.qml | 177 ++++++++++ client/ui/qml/Controls2/BackButtonType.qml | 2 + client/ui/qml/Controls2/DropDownType.qml | 13 +- client/ui/qml/Controls2/ListViewType.qml | 107 ++++++ .../ui/qml/Controls2/VerticalRadioButton.qml | 94 ++--- client/ui/qml/Pages2/PageHome.qml | 4 +- .../qml/Pages2/PageSettingsServerProtocol.qml | 59 +++- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 2 +- .../qml/Pages2/PageSetupWizardInstalling.qml | 2 +- .../PageSetupWizardProtocolSettings.qml | 2 +- .../qml/Pages2/PageSetupWizardProtocols.qml | 2 +- client/ui/qml/Pages2/PageShare.qml | 327 +++++++++++++++++- client/ui/qml/Pages2/PageStart.qml | 4 +- 38 files changed, 1080 insertions(+), 115 deletions(-) delete mode 100644 client/images/controls/check_off.png delete mode 100644 client/images/controls/check_on.png create mode 100644 client/images/controls/radio-button-inner-circle-pressed.png create mode 100644 client/images/controls/radio-button-inner-circle.png create mode 100644 client/images/controls/radio-button-pressed.svg create mode 100644 client/images/controls/radio-button.svg delete mode 100644 client/images/controls/radio_off.png delete mode 100644 client/images/controls/radio_on.png create mode 100644 client/ui/controllers/exportController.cpp create mode 100644 client/ui/controllers/exportController.h create mode 100644 client/ui/controllers/protocolSettingsController.cpp create mode 100644 client/ui/controllers/protocolSettingsController.h create mode 100644 client/ui/qml/Components/ShareConnectionDrawer.qml create mode 100644 client/ui/qml/Controls2/ListViewType.qml diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index dbeaac38..598645d8 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -46,7 +46,6 @@ #endif m_settings = std::shared_ptr(new Settings); - m_configurator = std::shared_ptr(new VpnConfigurator(m_settings, this)); } AmneziaApplication::~AmneziaApplication() @@ -80,10 +79,10 @@ void AmneziaApplication::init() m_serversModel.reset(new ServersModel(m_settings, this)); m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get()); + m_configurator = std::shared_ptr(new VpnConfigurator(m_settings, this)); m_vpnConnection.reset(new VpnConnection(m_settings, m_configurator)); m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel, m_vpnConnection)); - m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get()); 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_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); diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 0fd6842c..510f580f 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -13,12 +13,13 @@ #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/pageController.h" -#include "ui/controllers/installController.h" +#include "ui/controllers/exportController.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(QCoreApplication::instance())) @@ -71,7 +72,7 @@ private: QScopedPointer m_pageController; QScopedPointer m_installController; QScopedPointer m_importController; - + QScopedPointer m_exportController; }; #endif // AMNEZIA_APPLICATION_H diff --git a/client/images/controls/check_off.png b/client/images/controls/check_off.png deleted file mode 100644 index 0a7fbf709ea09e47560231777967e2308702adad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17930 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 e0OgL1C<3cKPR^?j<#awf_=36S6b diff --git a/client/images/controls/check_on.png b/client/images/controls/check_on.png deleted file mode 100644 index 8b3b683b11f7d401dcbf33c7464c168180bf4f83..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18032 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 aD}jaet7)Ar2^CO>0Y_U`n?mb#+x`t5|GQ)W diff --git a/client/images/controls/radio-button-inner-circle-pressed.png b/client/images/controls/radio-button-inner-circle-pressed.png new file mode 100644 index 0000000000000000000000000000000000000000..efcd6f1f97111a9b48dc7276a95bead4aadba225 GIT binary patch literal 5125 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 literal 0 HcmV?d00001 diff --git a/client/images/controls/radio-button-inner-circle.png b/client/images/controls/radio-button-inner-circle.png new file mode 100644 index 0000000000000000000000000000000000000000..da29d54aa12cae758b9d6a8ed105d32ba7edebf7 GIT binary patch literal 7720 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 + + diff --git a/client/images/controls/radio-button.svg b/client/images/controls/radio-button.svg new file mode 100644 index 00000000..75b1b5b4 --- /dev/null +++ b/client/images/controls/radio-button.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/images/controls/radio_off.png b/client/images/controls/radio_off.png deleted file mode 100644 index 685980bdfd97a2c58261f3d3e1abc6fa1d0dbde7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 491 zcmV9;+9u;NR)d-VWlj)f*T94f;_JM^ zYe~CLtJPZx9DtOvNv7ifZ1D|bWOPQ@7gM!Owx#SFfro}$?*u`Jy6YD6O65==3XXt= z!T3GPo^dRrR!hDPhmR&r60;Ay*6Vf+`>s!6Cu_IlERLScsbF7@v6Av`=a6laa_mmc zdbeG3DTafAVUAxyO8ZdFj)7x0Syt)%(skWt5QexE9NVb79Y9<=71EgJc_@n@3?PiT zaSUCs=ldv&wN{%B{uxZD7xM+mLgjhUnht#?(`kybP`P5GiveGG8QCN&+02-$sG%`< ziQ^cTVmul#$N0}AXBkD2ITswW-FlVHPpwvaqgvjPIIqGJMb4*+<499>_siw-YsX>p zd~shV2WrY|+`qQxOW1ZMr@jyvHK-*$&xhx)I*H{qO;da><`b@aW_e((gj>WCEA_Kw hooO-?BGUU}egJE$3_uvVdIJCe002ovPDHLkV1feU&3pg= diff --git a/client/images/controls/radio_on.png b/client/images/controls/radio_on.png deleted file mode 100644 index 48560e537843f963e8e0360c77b49dc8220fadb2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 624 zcmV-$0+0QPP)IfyJ~7R$Umx2RJI9(0$THKLJ#+v9l}i1;C=o8vdX`&JUD$QIJ3? z85V^;7O|>6Qspt7>pE%19oy|$*fV$TJ$LThhk-1tw;51$0KuZYAp#6cjt@&gnu(z& z+NsKKg;_<%S)2uXP>jUEzaE199(Z@arIQ*qmg>m8(fW?>eC!B+_e7+D&e*$Of^@H< zNG1Exakmb|ZQ*Vm2(rpB%^3u-|pVfBYFJpFyq{~ zn=4NhAGL3XD1YW?{W(S08$~i&SHJAZvm-^rolow`!;izoFG;DkRo^nW`<$-E) z8dtR{n5k2?N(JO0>nR#V-|_VJo`Z#}Q6w%$~h`>T?WC%YgE6qVOJ;$*AWh{qT8wZD6sdaH4d>&92s zfOTcDMqM;6d@8Rv0WG<0=mxBwaE!NY8-oI)Zi>}8l^In=n#m>X+15fe=Ae)O0000< KMNUMnLSTXd5EgR) diff --git a/client/resources.qrc b/client/resources.qrc index e2f16853..d60acfdd 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -6,10 +6,6 @@ images/favorites_disabled.png images/favorites_enabled.png images/favorites_hover.png - images/controls/check_off.png - images/controls/check_on.png - images/controls/radio_off.png - images/controls/radio_on.png images/download.png images/upload.png images/tray/active.png @@ -257,5 +253,11 @@ ui/qml/Pages2/PageSettingsServerProtocol.qml ui/qml/Components/Protocols/OpenVpnSettings.qml ui/qml/Components/TransportProtoSelector.qml + ui/qml/Controls2/ListViewType.qml + images/controls/radio-button.svg + images/controls/radio-button-inner-circle.png + images/controls/radio-button-pressed.svg + images/controls/radio-button-inner-circle-pressed.png + ui/qml/Components/ShareConnectionDrawer.qml diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp new file mode 100644 index 00000000..9ff73f33 --- /dev/null +++ b/client/ui/controllers/exportController.cpp @@ -0,0 +1,156 @@ +#include "exportController.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qrcodegen.hpp" + +ExportController::ExportController(const QSharedPointer &serversModel, + const QSharedPointer &containersModel, + const std::shared_ptr &settings, + const std::shared_ptr &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( + m_containersModel->getCurrentlyProcessedContainerIndex()); + QModelIndex containerModelIndex = m_containersModel->index(container); + QJsonObject containerConfig = qvariant_cast( + 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 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 ExportController::generateQrCodeImageSeries(const QByteArray &data) +{ + double k = 850; + + quint8 chunksCount = std::ceil(data.size() / k); + QList 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()); +} diff --git a/client/ui/controllers/exportController.h b/client/ui/controllers/exportController.h new file mode 100644 index 00000000..c5054143 --- /dev/null +++ b/client/ui/controllers/exportController.h @@ -0,0 +1,44 @@ +#ifndef EXPORTCONTROLLER_H +#define EXPORTCONTROLLER_H + +#include + +#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, + const QSharedPointer &containersModel, + const std::shared_ptr &settings, + const std::shared_ptr &configurator, + QObject *parent = nullptr); + +public slots: + void generateFullAccessConfig(); + void generateConnectionConfig(); + QString getAmneziaCode(); + QList getQrCodes(); + + void saveFile(); + +signals: + void generateConfig(bool isFullAccess); + +private: + QList generateQrCodeImageSeries(const QByteArray &data); + QString svgToBase64(const QString &image); + + QSharedPointer m_serversModel; + QSharedPointer m_containersModel; + std::shared_ptr m_settings; + std::shared_ptr m_configurator; + + QString m_amneziaCode; + QList m_qrCodes; +}; + +#endif // EXPORTCONTROLLER_H diff --git a/client/ui/controllers/importController.h b/client/ui/controllers/importController.h index 114bb531..561ea19c 100644 --- a/client/ui/controllers/importController.h +++ b/client/ui/controllers/importController.h @@ -27,6 +27,7 @@ public slots: signals: void importFinished(); void importErrorOccurred(QString errorMessage); + private: QJsonObject extractAmneziaConfig(QString &data); QJsonObject extractOpenVpnConfig(const QString &data); diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 2fe96962..8f9e1f88 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -9,8 +9,7 @@ InstallController::InstallController(const QSharedPointer &servers const QSharedPointer &containersModel, const std::shared_ptr &settings, QObject *parent) : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings) -{ -} +{} void InstallController::install(DockerContainer container, int port, TransportProto transportProto) { diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 946c1ce5..dcf2fc66 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -24,7 +24,6 @@ namespace PageLoader PageSettingsServerProtocol, PageSetupWizardStart, - PageTest, PageSetupWizardCredentials, PageSetupWizardProtocols, PageSetupWizardEasy, diff --git a/client/ui/controllers/protocolSettingsController.cpp b/client/ui/controllers/protocolSettingsController.cpp new file mode 100644 index 00000000..48414021 --- /dev/null +++ b/client/ui/controllers/protocolSettingsController.cpp @@ -0,0 +1,19 @@ +#include "protocolSettingsController.h" + +ProtocolSettingsController::ProtocolSettingsController( + const QSharedPointer &serversModel, + const QSharedPointer &containersModel, + const std::shared_ptr &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); +} diff --git a/client/ui/controllers/protocolSettingsController.h b/client/ui/controllers/protocolSettingsController.h new file mode 100644 index 00000000..730cbda7 --- /dev/null +++ b/client/ui/controllers/protocolSettingsController.h @@ -0,0 +1,31 @@ +#ifndef PROTOCOLSETTINGSCONTROLLER_H +#define PROTOCOLSETTINGSCONTROLLER_H + +#include + +#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, + const QSharedPointer &containersModel, + const std::shared_ptr &settings, + QObject *parent = nullptr); + +public slots: + QByteArray getOpenVpnConfig(); + +signals: + +private: + QSharedPointer m_serversModel; + QSharedPointer m_containersModel; + std::shared_ptr m_settings; +}; + +#endif // PROTOCOLSETTINGSCONTROLLER_H diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index b6574439..1caf6944 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -25,10 +25,10 @@ bool ContainersModel::setData(const QModelIndex &index, const QVariant &value, i // return ContainerProps::containerHumanNames().value(container); case DescRole: // return ContainerProps::containerDescriptions().value(container); - case ConfigRole: - m_settings->setContainerConfig(m_currentlyProcessedServerIndex, - container, - value.toJsonObject()); + case ConfigRole: //todo save to model also + m_settings->setContainerConfig(m_currentlyProcessedServerIndex, + container, + value.toJsonObject()); case ServiceTypeRole: // return ContainerProps::containerService(container); case DockerContainerRole: @@ -76,8 +76,8 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const return ContainerProps::easySetupDescription(container); case IsInstalledRole: return m_containers.contains(container); - case IsCurrentlyInstalledRole: - return container == static_cast(m_currentlyInstalledContainerIndex); + case IsCurrentlyProcessedRole: + return container == static_cast(m_currentlyProcessedContainerIndex); case IsDefaultRole: return container == m_defaultContainerIndex; case IsSupportedRole: @@ -97,9 +97,9 @@ void ContainersModel::setCurrentlyProcessedServerIndex(int index) emit defaultContainerChanged(); } -void ContainersModel::setCurrentlyInstalledContainerIndex(int index) +void ContainersModel::setCurrentlyProcessedContainerIndex(int index) { - m_currentlyInstalledContainerIndex = index; + m_currentlyProcessedContainerIndex = index; } DockerContainer ContainersModel::getDefaultContainer() @@ -112,9 +112,9 @@ QString ContainersModel::getDefaultContainerName() return ContainerProps::containerHumanNames().value(m_defaultContainerIndex); } -int ContainersModel::getCurrentlyInstalledContainerIndex() +int ContainersModel::getCurrentlyProcessedContainerIndex() { - return m_currentlyInstalledContainerIndex; + return m_currentlyProcessedContainerIndex; } void ContainersModel::removeAllContainers() @@ -153,7 +153,7 @@ QHash ContainersModel::roleNames() const { roles[EasySetupDescriptionRole] = "easySetupDescription"; roles[IsInstalledRole] = "isInstalled"; - roles[IsCurrentlyInstalledRole] = "isCurrentlyInstalled"; + roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed"; roles[IsDefaultRole] = "isDefault"; roles[IsSupportedRole] = "isSupported"; return roles; diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index 5753a1dd..7bb58755 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -27,7 +27,7 @@ public: EasySetupDescriptionRole, IsInstalledRole, - IsCurrentlyInstalledRole, + IsCurrentlyProcessedRole, IsDefaultRole, IsSupportedRole }; @@ -45,8 +45,8 @@ public slots: QString getDefaultContainerName(); void setCurrentlyProcessedServerIndex(int index); - void setCurrentlyInstalledContainerIndex(int index); - int getCurrentlyInstalledContainerIndex(); + void setCurrentlyProcessedContainerIndex(int index); + int getCurrentlyProcessedContainerIndex(); void removeAllContainers(); void clearCachedProfiles(); @@ -59,7 +59,7 @@ private: int m_currentlyProcessedServerIndex; - int m_currentlyInstalledContainerIndex; + int m_currentlyProcessedContainerIndex; DockerContainer m_defaultContainerIndex; std::shared_ptr m_settings; diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index bb08e88c..9ef87d37 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -75,6 +75,11 @@ void ServersModel::setCurrentlyProcessedServerIndex(int index) m_currenlyProcessedServerIndex = index; } +int ServersModel::getCurrentlyProcessedServerIndex() +{ + return m_currenlyProcessedServerIndex; +} + bool ServersModel::isDefaultServerCurrentlyProcessed() { return m_defaultServerIndex == m_currenlyProcessedServerIndex; diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index 593babc3..6f55176e 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -37,6 +37,7 @@ public slots: const int getServersCount(); void setCurrentlyProcessedServerIndex(int index); + int getCurrentlyProcessedServerIndex(); ServerCredentials getCurrentlyProcessedServerCredentials(); void addServer(const QJsonObject &server); diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index 926302c4..28f81b2e 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -59,7 +59,7 @@ ListView { menuContent.currentIndex = index containersDropDown.menuVisible = false } else { - ContainersModel.setCurrentlyInstalledContainerIndex(proxyContainersModel.mapToSource(index)) + ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(index)) InstallController.setShouldCreateServer(false) goToPage(PageEnum.PageSetupWizardProtocolSettings) containersDropDown.menuVisible = false diff --git a/client/ui/qml/Components/Protocols/OpenVpnSettings.qml b/client/ui/qml/Components/Protocols/OpenVpnSettings.qml index 8c036fc0..5c574832 100644 --- a/client/ui/qml/Components/Protocols/OpenVpnSettings.qml +++ b/client/ui/qml/Components/Protocols/OpenVpnSettings.qml @@ -8,8 +8,12 @@ import "../../Components" Item { id: root + implicitHeight: col.implicitHeight + implicitWidth: col.implicitWidth ColumnLayout { + id: col + anchors.fill: parent anchors.leftMargin: 16 @@ -51,13 +55,79 @@ Item { } DropDownType { + id: hash 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 { + id: cipher 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 { diff --git a/client/ui/qml/Components/SettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml index d5664541..1b8ea40c 100644 --- a/client/ui/qml/Components/SettingsContainersListView.qml +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -88,9 +88,10 @@ ListView { onClicked: { if (isInstalled) { + ContainersModel.setCurrentlyProcessedContainerIndex(root.model.mapToSource(index)) goToPage(PageEnum.PageSettingsServerProtocol) } else { - ContainersModel.setCurrentlyInstalledContainerIndex(root.model.mapToSource(index)) + ContainersModel.setCurrentlyProcessedContainerIndex(root.model.mapToSource(index)) InstallController.setShouldCreateServer(false) goToPage(PageEnum.PageSetupWizardProtocolSettings) } diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml new file mode 100644 index 00000000..5c9ceee8 --- /dev/null +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -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\"") + } + } + } + } +} diff --git a/client/ui/qml/Controls2/BackButtonType.qml b/client/ui/qml/Controls2/BackButtonType.qml index 389191e8..0ccb7345 100644 --- a/client/ui/qml/Controls2/BackButtonType.qml +++ b/client/ui/qml/Controls2/BackButtonType.qml @@ -11,6 +11,8 @@ Item { implicitWidth: content.implicitWidth implicitHeight: content.implicitHeight + visible: backButtonImage !== "" + RowLayout { id: content diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 700d9981..6456bbad 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -19,11 +19,12 @@ Item { property string rootButtonImage: "qrc:/images/controls/chevron-down.svg" property string rootButtonImageColor: "#494B50" property string rootButtonDefaultColor: "#1C1D21" - property int rootButtonMaximumWidth + property int rootButtonMaximumWidth: 0 property string rootButtonBorderColor: "#494B50" property int rootButtonBorderWidth: 1 + property real drawerHeight: 0.9 property Component listView property alias menuVisible: menu.visible @@ -55,7 +56,9 @@ Item { Layout.leftMargin: 16 LabelTextType { - horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + + horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter visible: root.descriptionText !== "" @@ -65,7 +68,9 @@ Item { } ButtonTextType { - horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + + horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter Layout.maximumWidth: rootButtonMaximumWidth ? rootButtonMaximumWidth : implicitWidth @@ -115,7 +120,7 @@ Item { id: menu width: parent.width - height: parent.height * 0.9 + height: parent.height * drawerHeight ColumnLayout { id: header diff --git a/client/ui/qml/Controls2/ListViewType.qml b/client/ui/qml/Controls2/ListViewType.qml new file mode 100644 index 00000000..421b82a3 --- /dev/null +++ b/client/ui/qml/Controls2/ListViewType.qml @@ -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 + } + } + } +} diff --git a/client/ui/qml/Controls2/VerticalRadioButton.qml b/client/ui/qml/Controls2/VerticalRadioButton.qml index 50af3c79..7f2411fb 100644 --- a/client/ui/qml/Controls2/VerticalRadioButton.qml +++ b/client/ui/qml/Controls2/VerticalRadioButton.qml @@ -23,12 +23,6 @@ RadioButton { property string defaultBodredColor: "transparent" 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 bool showImage @@ -61,80 +55,38 @@ RadioButton { } Image { - source: imageSource - visible: showImage + source: { + if (showImage) { + return imageSource + } else if (root.pressed) { + return "qrc:/images/controls/radio-button-inner-circle-pressed.png" + } else if (root.checked) { + return "qrc:/images/controls/radio-button-inner-circle.png" + } + + return "" + } anchors.centerIn: parent width: 24 height: 24 } - - Rectangle { - id: outerCircle - - width: 24 - height: 24 - radius: 16 - - visible: !showImage + Image { + source: { + if (showImage) { + return "" + } else if (root.pressed || root.checked) { + return "qrc:/images/controls/radio-button-pressed.svg" + } else { + return "qrc:/images/controls/radio-button.svg" + } + } anchors.centerIn: parent - color: "transparent" - border.color: { - if (root.enabled) { - if (root.pressed) { - return pressedCircleBorderColor - } else if (root.checked) { - return selectedCircleBorderColor - } - } - return defaultCircleBorderColor - } - - border.width: 1 - - Behavior on border.color { - PropertyAnimation { duration: 200 } - } - - Rectangle { - id: innerCircle - - width: 12 - height: 12 - radius: 16 - - anchors.centerIn: parent - - color: "transparent" - border.color: defaultInnerCircleColor - border.width: { - if (root.enabled) { - if(root.checked) { - return 6 - } - return root.pressed ? 6 : 0 - } else { - return 0 - } - } - - Behavior on border.width { - PropertyAnimation { duration: 200 } - } - } - - DropShadow { - anchors.fill: innerCircle - horizontalOffset: 0 - verticalOffset: 0 - radius: 12 - samples: 13 - color: "#FBB26A" - source: innerCircle - } + width: 24 + height: 24 } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 23ca9e01..780f3aef 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -111,7 +111,7 @@ PageType { id: menu width: parent.width - height: parent.height * 0.90 + height: parent.height * 0.9 ColumnLayout { id: serversMenuHeader @@ -247,8 +247,8 @@ PageType { ColumnLayout { id: serverRadioButtonContent - anchors.fill: parent + anchors.fill: parent anchors.rightMargin: 16 anchors.leftMargin: 16 diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml index 800041f0..498006df 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -18,7 +18,64 @@ import "../Components/Protocols" PageType { id: root - OpenVpnSettings { + FlickableType { + id: fl 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 + } + } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index 99f6ccfb..1836f892 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -134,7 +134,7 @@ PageType { text: qsTr("Continue") onClicked: function() { - ContainersModel.setCurrentlyInstalledContainerIndex(containers.dockerContainer) + ContainersModel.setCurrentlyProcessedContainerIndex(containers.dockerContainer) goToPage(PageEnum.PageSetupWizardInstalling); InstallController.install(containers.dockerContainer, containers.containerDefaultPort, diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index c18c4d27..9631e258 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -54,7 +54,7 @@ PageType { sourceModel: ContainersModel filters: [ ValueFilter { - roleName: "isCurrentlyInstalled" + roleName: "isCurrentlyProcessed" value: true } ] diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 57fa497d..c0483df4 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -22,7 +22,7 @@ PageType { sourceModel: ContainersModel filters: [ ValueFilter { - roleName: "isCurrentlyInstalled" + roleName: "isCurrentlyProcessed" value: true } ] diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index 75b16afd..e2ae4a16 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -90,7 +90,7 @@ PageType { buttonImage: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { - ContainersModel.setCurrentlyInstalledContainerIndex(proxyContainersModel.mapToSource(index)) + ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(index)) goToPage(PageEnum.PageSetupWizardProtocolSettings) } } diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 5560aee7..a731e425 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -1,5 +1,330 @@ 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 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 + } + } + } + } } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 16dd00d3..8b9cefa4 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -78,7 +78,9 @@ PageType { TabImageButtonType { isSelected: tabBar.currentIndex === 1 image: "qrc:/images/controls/share-2.svg" - onClicked: {} + onClicked: { + tabBarStackView.goToTabBarPage(PageEnum.PageShare) + } } TabImageButtonType { isSelected: tabBar.currentIndex === 2 From 7b14ad9616f4437b4569c10659c07d634c500300 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 16 Jun 2023 13:43:55 +0900 Subject: [PATCH 032/278] added PageSettingsAbout, PageSettingsApplication, PageSettingsBackup, PageSettingsConnection, PageSettingsDns - added SettingsController --- client/amnezia_application.cpp | 4 + client/amnezia_application.h | 2 + client/images/controls/delete.svg | 5 + client/images/controls/github.svg | 4 + client/images/controls/mail.svg | 4 + client/images/controls/telegram.svg | 3 + client/resources.qrc | 9 + client/ui/controllers/exportController.cpp | 42 ++-- client/ui/controllers/exportController.h | 10 + client/ui/controllers/pageController.h | 5 + client/ui/controllers/settingsController.cpp | 113 ++++++++++ client/ui/controllers/settingsController.h | 56 +++++ .../Components/Protocols/OpenVpnSettings.qml | 4 - .../qml/Components/ShareConnectionDrawer.qml | 14 +- client/ui/qml/Controls2/DropDownType.qml | 44 +++- client/ui/qml/Controls2/ImageButtonType.qml | 9 +- client/ui/qml/Controls2/SwitcherType.qml | 37 +++- client/ui/qml/Pages2/PageHome.qml | 4 +- client/ui/qml/Pages2/PageSettings.qml | 4 + client/ui/qml/Pages2/PageSettingsAbout.qml | 207 ++++++++++++++++++ .../ui/qml/Pages2/PageSettingsApplication.qml | 74 +++++++ client/ui/qml/Pages2/PageSettingsBackup.qml | 184 ++++++++++++++++ .../ui/qml/Pages2/PageSettingsConnection.qml | 107 +++++++++ client/ui/qml/Pages2/PageSettingsDns.qml | 87 ++++++++ client/ui/qml/Pages2/PageShare.qml | 10 +- client/utilities.cpp | 54 +++++ client/utilities.h | 15 +- service/server/CMakeLists.txt | 6 +- 28 files changed, 1054 insertions(+), 63 deletions(-) create mode 100644 client/images/controls/delete.svg create mode 100644 client/images/controls/github.svg create mode 100644 client/images/controls/mail.svg create mode 100644 client/images/controls/telegram.svg create mode 100644 client/ui/controllers/settingsController.cpp create mode 100644 client/ui/controllers/settingsController.h create mode 100644 client/ui/qml/Pages2/PageSettingsAbout.qml create mode 100644 client/ui/qml/Pages2/PageSettingsApplication.qml create mode 100644 client/ui/qml/Pages2/PageSettingsBackup.qml create mode 100644 client/ui/qml/Pages2/PageSettingsConnection.qml create mode 100644 client/ui/qml/Pages2/PageSettingsDns.qml diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 598645d8..b9aa0f74 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -98,6 +98,10 @@ void AmneziaApplication::init() new ExportController(m_serversModel, m_containersModel, m_settings, m_configurator)); m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get()); + m_settingsController.reset( + new SettingsController(m_serversModel, m_containersModel, m_settings)); + m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get()); + // m_engine->load(url); diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 510f580f..893511b6 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -18,6 +18,7 @@ #include "ui/controllers/importController.h" #include "ui/controllers/installController.h" #include "ui/controllers/pageController.h" +#include "ui/controllers/settingsController.h" #include "ui/models/containers_model.h" #include "ui/models/servers_model.h" @@ -73,6 +74,7 @@ private: QScopedPointer m_installController; QScopedPointer m_importController; QScopedPointer m_exportController; + QScopedPointer m_settingsController; }; #endif // AMNEZIA_APPLICATION_H diff --git a/client/images/controls/delete.svg b/client/images/controls/delete.svg new file mode 100644 index 00000000..78ef3eea --- /dev/null +++ b/client/images/controls/delete.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/images/controls/github.svg b/client/images/controls/github.svg new file mode 100644 index 00000000..7b1f250a --- /dev/null +++ b/client/images/controls/github.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/images/controls/mail.svg b/client/images/controls/mail.svg new file mode 100644 index 00000000..1debe4f1 --- /dev/null +++ b/client/images/controls/mail.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/images/controls/telegram.svg b/client/images/controls/telegram.svg new file mode 100644 index 00000000..7b76e506 --- /dev/null +++ b/client/images/controls/telegram.svg @@ -0,0 +1,3 @@ + + + diff --git a/client/resources.qrc b/client/resources.qrc index d60acfdd..a8f8718e 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -259,5 +259,14 @@ images/controls/radio-button-pressed.svg images/controls/radio-button-inner-circle-pressed.png ui/qml/Components/ShareConnectionDrawer.qml + ui/qml/Pages2/PageSettingsConnection.qml + ui/qml/Pages2/PageSettingsDns.qml + ui/qml/Pages2/PageSettingsApplication.qml + ui/qml/Pages2/PageSettingsBackup.qml + images/controls/delete.svg + ui/qml/Pages2/PageSettingsAbout.qml + images/controls/github.svg + images/controls/mail.svg + images/controls/telegram.svg diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index 9ff73f33..98cbebd1 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -11,6 +11,8 @@ #include "qrcodegen.hpp" +#include "core/errorstrings.h" + ExportController::ExportController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, const std::shared_ptr &settings, @@ -35,6 +37,7 @@ void ExportController::generateFullAccessConfig() | QByteArray::OmitTrailingEquals))); m_qrCodes = generateQrCodeImageSeries(compressedConfig); + emit exportConfigChanged(); } void ExportController::generateConnectionConfig() @@ -49,25 +52,25 @@ void ExportController::generateConnectionConfig() 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); + ErrorCode errorCode = ErrorCode::NoError; + for (Proto protocol : ContainerProps::protocolsForContainer(container)) { + QJsonObject protocolConfig = m_settings->protocolConfig(serverIndex, container, protocol); - QString cfg = m_configurator->genVpnProtocolConfig(credentials, - container, - containerConfig, - p, - &e); - if (e) { - cfg = "Error generating config"; - break; + QString vpnConfig = m_configurator->genVpnProtocolConfig(credentials, + container, + containerConfig, + protocol, + &errorCode); + if (errorCode) { + emit exportErrorOccurred(errorString(errorCode)); + return; } - protoConfig.insert(config_key::last_config, cfg); - containerConfig.insert(ProtocolProps::protoToString(p), protoConfig); + protocolConfig.insert(config_key::last_config, vpnConfig); + containerConfig.insert(ProtocolProps::protoToString(protocol), protocolConfig); } QJsonObject config = m_settings->server(serverIndex); - if (!e) { + if (!errorCode) { config.remove(config_key::userName); config.remove(config_key::password); config.remove(config_key::port); @@ -77,11 +80,8 @@ void ExportController::generateConnectionConfig() 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") @@ -89,6 +89,7 @@ void ExportController::generateConnectionConfig() | QByteArray::OmitTrailingEquals))); m_qrCodes = generateQrCodeImageSeries(compressedConfig); + emit exportConfigChanged(); } QString ExportController::getAmneziaCode() @@ -154,3 +155,8 @@ QString ExportController::svgToBase64(const QString &image) { return "data:image/svg;base64," + QString::fromLatin1(image.toUtf8().toBase64().data()); } + +int ExportController::getQrCodesCount() +{ + return m_qrCodes.size(); +} diff --git a/client/ui/controllers/exportController.h b/client/ui/controllers/exportController.h index c5054143..63997efd 100644 --- a/client/ui/controllers/exportController.h +++ b/client/ui/controllers/exportController.h @@ -17,9 +17,14 @@ public: const std::shared_ptr &configurator, QObject *parent = nullptr); + Q_PROPERTY(QList qrCodes READ getQrCodes NOTIFY exportConfigChanged) + Q_PROPERTY(int qrCodesCount READ getQrCodesCount NOTIFY exportConfigChanged) + Q_PROPERTY(QString amneziaCode READ getAmneziaCode NOTIFY exportConfigChanged) + public slots: void generateFullAccessConfig(); void generateConnectionConfig(); + QString getAmneziaCode(); QList getQrCodes(); @@ -27,11 +32,16 @@ public slots: signals: void generateConfig(bool isFullAccess); + void exportErrorOccurred(QString errorMessage); + + void exportConfigChanged(); private: QList generateQrCodeImageSeries(const QByteArray &data); QString svgToBase64(const QString &image); + int getQrCodesCount(); + QSharedPointer m_serversModel; QSharedPointer m_containersModel; std::shared_ptr m_settings; diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index dcf2fc66..587e0e38 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -22,6 +22,11 @@ namespace PageLoader PageSettingsServerProtocols, PageSettingsServerServices, PageSettingsServerProtocol, + PageSettingsConnection, + PageSettingsDns, + PageSettingsApplication, + PageSettingsBackup, + PageSettingsAbout, PageSetupWizardStart, PageSetupWizardCredentials, diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp new file mode 100644 index 00000000..b86b1cff --- /dev/null +++ b/client/ui/controllers/settingsController.cpp @@ -0,0 +1,113 @@ +#include "settingsController.h" + +#include + +#include "defines.h" +#include "logger.h" +#include "utilities.h" + +SettingsController::SettingsController(const QSharedPointer &serversModel, + const QSharedPointer &containersModel, + const std::shared_ptr &settings, + QObject *parent) + : QObject(parent) + , m_serversModel(serversModel) + , m_containersModel(containersModel) + , m_settings(settings) +{ + m_appVersion = QString("%1: %2 (%3)") + .arg(tr("Software version"), QString(APP_MAJOR_VERSION), __DATE__); +} + +void SettingsController::setAmneziaDns(bool enable) +{ + m_settings->setUseAmneziaDns(enable); +} + +bool SettingsController::isAmneziaDnsEnabled() +{ + return m_settings->useAmneziaDns(); +} + +QString SettingsController::getPrimaryDns() +{ + return m_settings->primaryDns(); +} + +void SettingsController::setPrimaryDns(const QString &dns) +{ + m_settings->setPrimaryDns(dns); + emit primaryDnsChanged(); +} + +QString SettingsController::getSecondaryDns() +{ + return m_settings->secondaryDns(); +} + +void SettingsController::setSecondaryDns(const QString &dns) +{ + return m_settings->setSecondaryDns(dns); + emit secondaryDnsChanged(); +} + +bool SettingsController::isSaveLogsEnabled() +{ + return m_settings->isSaveLogs(); +} + +void SettingsController::setSaveLogs(bool enable) +{ + m_settings->setSaveLogs(enable); +} + +void SettingsController::openLogsFolder() +{ + Logger::openLogsFolder(); +} + +void SettingsController::exportLogsFile() +{ + Utils::saveFile(".log", tr("Save log"), "AmneziaVPN", Logger::getLogFile()); +} + +void SettingsController::clearLogs() +{ + Logger::clearLogs(); + Logger::clearServiceLogs(); +} + +void SettingsController::backupAppConfig() +{ + Utils::saveFile(".backup", + tr("Backup application config"), + "AmneziaVPN", + m_settings->backupAppConfig()); +} + +void SettingsController::restoreAppConfig() +{ + QString fileName = Utils::getFileName(Q_NULLPTR, + tr("Open backup"), + QStandardPaths::writableLocation( + QStandardPaths::DocumentsLocation), + "*.backup"); + + //todo error processing + if (fileName.isEmpty()) + return; + + QFile file(fileName); + file.open(QIODevice::ReadOnly); + QByteArray data = file.readAll(); + + bool ok = m_settings->restoreAppConfig(data); + if (ok) { + // emit uiLogic()->showWarningMessage(tr("Can't import config, file is corrupted.")); + } +} + +QString SettingsController::getAppVersion() +{ + return m_appVersion; +} diff --git a/client/ui/controllers/settingsController.h b/client/ui/controllers/settingsController.h new file mode 100644 index 00000000..77de424c --- /dev/null +++ b/client/ui/controllers/settingsController.h @@ -0,0 +1,56 @@ +#ifndef SETTINGSCONTROLLER_H +#define SETTINGSCONTROLLER_H + +#include + +#include "ui/models/containers_model.h" +#include "ui/models/servers_model.h" + +class SettingsController : public QObject +{ + Q_OBJECT +public: + explicit SettingsController(const QSharedPointer &serversModel, + const QSharedPointer &containersModel, + const std::shared_ptr &settings, + QObject *parent = nullptr); + + Q_PROPERTY(QString primaryDns READ getPrimaryDns WRITE setPrimaryDns NOTIFY primaryDnsChanged) + Q_PROPERTY( + QString secondaryDns READ getSecondaryDns WRITE setSecondaryDns NOTIFY secondaryDnsChanged) + +public slots: + void setAmneziaDns(bool enable); + bool isAmneziaDnsEnabled(); + + QString getPrimaryDns(); + void setPrimaryDns(const QString &dns); + + QString getSecondaryDns(); + void setSecondaryDns(const QString &dns); + + bool isSaveLogsEnabled(); + void setSaveLogs(bool enable); + + void openLogsFolder(); + void exportLogsFile(); + void clearLogs(); + + void backupAppConfig(); + void restoreAppConfig(); + + QString getAppVersion(); + +signals: + void primaryDnsChanged(); + void secondaryDnsChanged(); + +private: + QSharedPointer m_serversModel; + QSharedPointer m_containersModel; + std::shared_ptr m_settings; + + QString m_appVersion; +}; + +#endif // SETTINGSCONTROLLER_H diff --git a/client/ui/qml/Components/Protocols/OpenVpnSettings.qml b/client/ui/qml/Components/Protocols/OpenVpnSettings.qml index 5c574832..f937dcfc 100644 --- a/client/ui/qml/Components/Protocols/OpenVpnSettings.qml +++ b/client/ui/qml/Components/Protocols/OpenVpnSettings.qml @@ -59,8 +59,6 @@ Item { Layout.fillWidth: true implicitHeight: 74 - rootButtonBorderWidth: 0 - descriptionText: qsTr("Hash") headerText: qsTr("Hash") @@ -97,8 +95,6 @@ Item { Layout.fillWidth: true implicitHeight: 74 - rootButtonBorderWidth: 0 - descriptionText: qsTr("Cipher") headerText: qsTr("Cipher") diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 5c9ceee8..1b92c752 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -15,8 +15,6 @@ import "../Controls2/TextTypes" DrawerType { id: root - property var qrCodes: [] - property alias configText: configContent.text property alias headerText: header.headerText width: parent.width @@ -120,6 +118,8 @@ DrawerType { font.weight: Font.Medium font.family: "PT Root UI VF" + text: ExportController.amneziaCode + wrapMode: Text.Wrap enabled: false @@ -135,6 +135,8 @@ DrawerType { Layout.preferredHeight: width Layout.topMargin: 20 + visible: ExportController.qrCodesCount > 0 + color: "white" Image { @@ -144,22 +146,20 @@ DrawerType { Timer { property int idx: 0 interval: 1000 - running: qrCodes.length > 0 + running: ExportController.qrCodesCount > 0 repeat: true onTriggered: { idx++ - if (idx >= qrCodes.length) { + if (idx >= ExportController.qrCodesCount) { idx = 0 } - parent.source = qrCodes[idx] + parent.source = ExportController.qrCodes[idx] } } Behavior on source { PropertyAnimation { duration: 200 } } - - visible: qrCodes.length > 0 } } diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 6456bbad..38cd0afa 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -15,13 +15,15 @@ Item { property string headerText property string headerBackButtonImage - property var onRootButtonClicked + property var rootButtonClickedFunction property string rootButtonImage: "qrc:/images/controls/chevron-down.svg" - property string rootButtonImageColor: "#494B50" - property string rootButtonDefaultColor: "#1C1D21" + property string rootButtonImageColor: "#D7D8DB" + property string rootButtonBackgroundColor: "#1C1D21" property int rootButtonMaximumWidth: 0 - property string rootButtonBorderColor: "#494B50" + property string rootButtonHoveredBorderColor: "#494B50" + property string rootButtonDefaultBorderColor: "transparent" + property string rootButtonPressedBorderColor: "#D7D8DB" property int rootButtonBorderWidth: 1 property real drawerHeight: 0.9 @@ -32,18 +34,31 @@ Item { implicitWidth: rootButtonContent.implicitWidth implicitHeight: rootButtonContent.implicitHeight + onMenuVisibleChanged: { + if (menuVisible) { + rootButtonBackground.border.color = rootButtonPressedBorderColor + rootButtonBackground.border.width = rootButtonBorderWidth + } else { + rootButtonBackground.border.color = rootButtonDefaultBorderColor + rootButtonBackground.border.width = 0 + } + } + Rectangle { id: rootButtonBackground anchors.fill: rootButtonContent radius: 16 - color: rootButtonDefaultColor - border.color: rootButtonBorderColor - border.width: rootButtonBorderWidth + color: rootButtonBackgroundColor + border.color: rootButtonDefaultBorderColor + border.width: 0 Behavior on border.width { PropertyAnimation { duration: 200 } } + Behavior on border.color { + PropertyAnimation { duration: 200 } + } } RowLayout { @@ -83,7 +98,6 @@ Item { } } - //todo change to image type ImageButtonType { Layout.leftMargin: 4 Layout.rightMargin: 16 @@ -100,16 +114,22 @@ Item { hoverEnabled: true onEntered: { - rootButtonBackground.border.width = rootButtonBorderWidth + if (menu.visible === false) { + rootButtonBackground.border.width = rootButtonBorderWidth + rootButtonBackground.border.color = rootButtonHoveredBorderColor + } } onExited: { - rootButtonBackground.border.width = 0 + if (menu.visible === false) { + rootButtonBackground.border.width = 0 + rootButtonBackground.border.color = rootButtonDefaultBorderColor + } } onClicked: { - if (onRootButtonClicked && typeof onRootButtonClicked === "function") { - onRootButtonClicked() + if (rootButtonClickedFunction && typeof rootButtonClickedFunction === "function") { + rootButtonClickedFunction() } else { menu.visible = true } diff --git a/client/ui/qml/Controls2/ImageButtonType.qml b/client/ui/qml/Controls2/ImageButtonType.qml index 72c78342..843599a4 100644 --- a/client/ui/qml/Controls2/ImageButtonType.qml +++ b/client/ui/qml/Controls2/ImageButtonType.qml @@ -10,8 +10,10 @@ Button { property string hoveredColor: Qt.rgba(1, 1, 1, 0.08) property string defaultColor: "transparent" property string pressedColor: Qt.rgba(1, 1, 1, 0.12) + property string disableColor: "#2C2D30" property string imageColor: "#878B91" + property string disableImageColor: "#2C2D30" implicitWidth: 40 implicitHeight: 40 @@ -19,7 +21,11 @@ Button { hoverEnabled: true icon.source: image - icon.color: imageColor + icon.color: root.enabled ? imageColor : disableImageColor + + Behavior on icon.color { + PropertyAnimation { duration: 200 } + } background: Rectangle { id: background @@ -33,6 +39,7 @@ Button { } return hovered ? hoveredColor : defaultColor } + return defaultColor } Behavior on color { PropertyAnimation { duration: 200 } diff --git a/client/ui/qml/Controls2/SwitcherType.qml b/client/ui/qml/Controls2/SwitcherType.qml index b593ece8..b63c64fb 100644 --- a/client/ui/qml/Controls2/SwitcherType.qml +++ b/client/ui/qml/Controls2/SwitcherType.qml @@ -2,9 +2,13 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import "TextTypes" + Switch { id: root + property alias descriptionText: description.text + property string checkedIndicatorColor: "#412102" property string defaultIndicatorColor: "transparent" property string checkedIndicatorBorderColor: "#412102" @@ -16,10 +20,18 @@ Switch { property string hoveredIndicatorBackgroundColor: Qt.rgba(1, 1, 1, 0.08) property string defaultIndicatorBackgroundColor: "transparent" + implicitWidth: content.implicitWidth + switcher.implicitWidth + implicitHeight: content.implicitHeight + indicator: Rectangle { + id: switcher + + anchors.left: content.right + anchors.verticalCenter: parent.verticalCenter + implicitWidth: 52 implicitHeight: 32 - x: content.width - width + radius: 16 color: root.checked ? checkedIndicatorColor : defaultIndicatorColor border.color: root.checked ? checkedIndicatorBorderColor : defaultIndicatorBorderColor @@ -62,16 +74,23 @@ Switch { contentItem: ColumnLayout { id: content - Text { - text: root.text - color: "#D7D8DB" - font.pixelSize: 18 - font.weight: 400 - font.family: "PT Root UI VF" + anchors.fill: parent + anchors.rightMargin: switcher.implicitWidth - height: 22 + ListItemTitleType { Layout.fillWidth: true - Layout.bottomMargin: 16 + + text: root.text + } + + CaptionTextType { + id: description + + Layout.fillWidth: true + + color: "#878B91" + + visible: text !== "" } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 780f3aef..9571b1fb 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -145,14 +145,14 @@ PageType { rootButtonBorderWidth: 0 rootButtonImageColor: "#0E0E11" rootButtonMaximumWidth: 150 //todo make it dynamic - rootButtonDefaultColor: "#D7D8DB" + rootButtonBackgroundColor: "#D7D8DB" text: root.currentContainerName textColor: "#0E0E11" headerText: qsTr("Протокол подключения") headerBackButtonImage: "qrc:/images/controls/arrow-left.svg" - onRootButtonClicked: function() { + rootButtonClickedFunction: function() { ServersModel.setCurrentlyProcessedServerIndex(serversMenuContent.currentIndex) ContainersModel.setCurrentlyProcessedServerIndex(serversMenuContent.currentIndex) containersDropDown.menuVisible = true diff --git a/client/ui/qml/Pages2/PageSettings.qml b/client/ui/qml/Pages2/PageSettings.qml index 300421b7..a7472580 100644 --- a/client/ui/qml/Pages2/PageSettings.qml +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -58,6 +58,7 @@ PageType { iconImage: "qrc:/images/controls/radio.svg" clickedFunction: function() { + goToPage(PageEnum.PageSettingsConnection) } } @@ -71,6 +72,7 @@ PageType { iconImage: "qrc:/images/controls/app.svg" clickedFunction: function() { + goToPage(PageEnum.PageSettingsApplication) } } @@ -84,6 +86,7 @@ PageType { iconImage: "qrc:/images/controls/save.svg" clickedFunction: function() { + goToPage(PageEnum.PageSettingsBackup) } } @@ -97,6 +100,7 @@ PageType { iconImage: "qrc:/images/controls/amnezia.svg" clickedFunction: function() { + goToPage(PageEnum.PageSettingsAbout) } } diff --git a/client/ui/qml/Pages2/PageSettingsAbout.qml b/client/ui/qml/Pages2/PageSettingsAbout.qml new file mode 100644 index 00000000..88c927b9 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsAbout.qml @@ -0,0 +1,207 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Config" +import "../Controls2/TextTypes" +import "../Components" + +PageType { + id: root + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + anchors.topMargin: 20 + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: root.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + Image { + id: image + source: "qrc:/images/amneziaBigLogo.png" + + Layout.alignment: Qt.AlignCenter + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + Layout.preferredWidth: 344 + Layout.preferredHeight: 279 + } + + Header2TextType { + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("Support the project with a donation") + horizontalAlignment: Text.AlignHCenter + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + horizontalAlignment: Text.AlignHCenter + + height: 20 + font.pixelSize: 14 + + text: qsTr("This is a free and open source application. If you like it, support the developers with a donation. +And if you don't like the app, all the more support it - the donation will be used to improve the app.") + color: "#CCCAC8" + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("Card on Patreon") + + onClicked: { + } + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 8 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + 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("Show other methods on Github") + + onClicked: { + } + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("Contacts") + } + + LabelWithButtonType { + Layout.fillWidth: true + Layout.topMargin: 16 + + text: qsTr("Telegram group") + descriptionText: qsTr("To discuss features") + buttonImage: "qrc:/images/controls/chevron-right.svg" + iconImage: "qrc:/images/controls/telegram.svg" + + clickedFunction: function() { + goToPage(PageEnum.PageSettingsAbout) + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Mail") + descriptionText: qsTr("For reviews and bug reports") + buttonImage: "qrc:/images/controls/chevron-right.svg" + iconImage: "qrc:/images/controls/mail.svg" + + clickedFunction: function() { + goToPage(PageEnum.PageSettingsAbout) + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Github") + buttonImage: "qrc:/images/controls/chevron-right.svg" + iconImage: "qrc:/images/controls/github.svg" + + clickedFunction: function() { + goToPage(PageEnum.PageSettingsAbout) + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Website") + buttonImage: "qrc:/images/controls/chevron-right.svg" + iconImage: "qrc:/images/controls/amnezia.svg" + + clickedFunction: function() { + goToPage(PageEnum.PageSettingsAbout) + } + } + + DividerType {} + + CaptionTextType { + Layout.fillWidth: true + Layout.topMargin: 40 + + horizontalAlignment: Text.AlignHCenter + + text: SettingsController.getAppVersion() + color: "#878B91" + } + + BasicButtonType { + Layout.alignment: Qt.AlignHCenter + Layout.topMargin: 8 + Layout.bottomMargin: 16 + implicitHeight: 32 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#FBB26A" + + text: qsTr("Check for updates") + + onClicked: { + Qt.openUrlExternally("https://github.com/amnezia-vpn/desktop-client/releases/latest") + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml new file mode 100644 index 00000000..9a6eab3c --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -0,0 +1,74 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Config" +import "../Controls2/TextTypes" + +PageType { + id: root + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + anchors.topMargin: 20 + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: root.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + spacing: 16 + + HeaderType { + Layout.fillWidth: true + + headerText: qsTr("Application") + } + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Language") + buttonImage: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Reset settings and remove all data from the application") + buttonImage: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + } + } + + DividerType {} + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml new file mode 100644 index 00000000..0cc62979 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -0,0 +1,184 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Config" +import "../Controls2/TextTypes" + +PageType { + id: root + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + anchors.topMargin: 20 + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: root.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + spacing: 16 + + HeaderType { + Layout.fillWidth: true + + headerText: qsTr("Backup") + } + + SwitcherType { + Layout.fillWidth: true + Layout.topMargin: 16 + + text: qsTr("Save logs") + + checked: SettingsController.isSaveLogsEnabled() + onCheckedChanged: { + if (checked !== SettingsController.isSaveLogsEnabled()) { + SettingsController.setSaveLogs(checked) + } + } + } + + RowLayout { + Layout.fillWidth: true + + ColumnLayout { + Layout.alignment: Qt.AlignBaseline + Layout.preferredWidth: root.width / 3 + + ImageButtonType { + Layout.alignment: Qt.AlignHCenter + + implicitWidth: 56 + implicitHeight: 56 + + image: "qrc:/images/controls/folder-open.svg" + + onClicked: SettingsController.openLogsFolder() + } + + CaptionTextType { + horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + + text: qsTr("Open folder with logs") + color: "#D7D8DB" + } + } + + ColumnLayout { + Layout.alignment: Qt.AlignBaseline + Layout.preferredWidth: root.width / 3 + + ImageButtonType { + Layout.alignment: Qt.AlignHCenter + + implicitWidth: 56 + implicitHeight: 56 + + image: "qrc:/images/controls/save.svg" + + onClicked: SettingsController.exportLogsFile() + } + + CaptionTextType { + horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + + text: qsTr("Save logs to file") + color: "#D7D8DB" + } + } + + ColumnLayout { + Layout.alignment: Qt.AlignBaseline + Layout.preferredWidth: root.width / 3 + + ImageButtonType { + Layout.alignment: Qt.AlignHCenter + + implicitWidth: 56 + implicitHeight: 56 + + image: "qrc:/images/controls/delete.svg" + + onClicked: SettingsController.clearLogs() + } + + CaptionTextType { + horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + + text: qsTr("Clear logs") + color: "#D7D8DB" + } + } + } + + ListItemTitleType { + Layout.fillWidth: true + Layout.topMargin: 10 + + text: qsTr("Configuration backup") + } + + CaptionTextType { + Layout.fillWidth: true + Layout.topMargin: -12 + + text: qsTr("It will help you instantly restore connection settings at the next installation") + color: "#878B91" + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 14 + + text: qsTr("Make a backup") + + onClicked: { + SettingsController.backupAppConfig() + } + } + + 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("Restore from backup") + + onClicked: { + SettingsController.restoreAppConfig() + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml new file mode 100644 index 00000000..fb0bb000 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -0,0 +1,107 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Config" + +PageType { + id: root + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + anchors.topMargin: 20 + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: root.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + spacing: 16 + + HeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: qsTr("Connection") + } + + SwitcherType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("Use AmnesiaDNS if installed on the server") + descriptionText: qsTr("Internal IP address 172.29.172.254") + + checked: SettingsController.isAmneziaDnsEnabled() + onCheckedChanged: { + if (checked !== SettingsController.isAmneziaDnsEnabled()) { + SettingsController.setAmneziaDns(checked) + } + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("DNS servers") + descriptionText: qsTr("If AmneziaDNS is not used or installed") + buttonImage: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + goToPage(PageEnum.PageSettingsDns) + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Split site tunneling") + descriptionText: qsTr("Allows you to connect to some sites through a secure connection, and to others bypassing it") + buttonImage: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Separate application tunneling") + descriptionText: qsTr("Allows you to use the VPN only for certain applications") + buttonImage: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + } + } + + DividerType {} + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsDns.qml b/client/ui/qml/Pages2/PageSettingsDns.qml new file mode 100644 index 00000000..2a06438b --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsDns.qml @@ -0,0 +1,87 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Config" +import "../Controls2/TextTypes" + +PageType { + id: root + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + anchors.topMargin: 20 + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: root.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + spacing: 16 + + HeaderType { + Layout.fillWidth: true + + headerText: qsTr("DNS servers") + } + + ParagraphTextType { + text: qsTr("If AmneziaDNS is not used or installed") + } + + TextFieldWithHeaderType { + id: primaryDns + + Layout.fillWidth: true + headerText: "Primary DNS" + + textFieldText: SettingsController.primaryDns + } + + TextFieldWithHeaderType { + id: secondaryDns + + Layout.fillWidth: true + headerText: "Secondary DNS" + + textFieldText: SettingsController.secondaryDns + } + + BasicButtonType { + Layout.fillWidth: true + + text: qsTr("Save") + + onClicked: function() { + if (primaryDns.textFieldText !== SettingsController.primaryDns) { + SettingsController.primaryDns = primaryDns.textFieldText + } + if (secondaryDns.textFieldText !== SettingsController.secondaryDns) { + SettingsController.secondaryDns = secondaryDns.textFieldText + } + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index a731e425..64641094 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -25,9 +25,6 @@ PageType { } else { ExportController.generateConnectionConfig() } - - shareConnectionDrawer.configText = ExportController.getAmneziaCode() - shareConnectionDrawer.qrCodes = ExportController.getQrCodes() } } @@ -125,7 +122,8 @@ PageType { ParagraphTextType { Layout.fillWidth: true - text: qsTr("VPN access without the ability to manage the server") + text: accessTypeSelector.currentIndex === 0 ? qsTr("VPN access without the ability to manage the server") : + qsTr("Full access to server") color: "#878B91" } @@ -137,7 +135,6 @@ PageType { implicitHeight: 74 - rootButtonBorderWidth: 0 drawerHeight: 0.4375 descriptionText: qsTr("Server and service") @@ -234,6 +231,7 @@ PageType { clickedFunction: function () { serverSelector.text += ", " + selectedText shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text + ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(currentIndex)) protocolSelector.visible = false serverSelector.menuVisible = false @@ -244,6 +242,7 @@ PageType { Component.onCompleted: { serverSelector.text += ", " + selectedText shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text + ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(currentIndex)) fillConnectionTypeModel() } @@ -273,7 +272,6 @@ PageType { implicitHeight: 74 - rootButtonBorderWidth: 0 drawerHeight: 0.4375 visible: accessTypeSelector.currentIndex === 0 diff --git a/client/utilities.cpp b/client/utilities.cpp index 13e36c2f..bf06ddac 100644 --- a/client/utilities.cpp +++ b/client/utilities.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -7,6 +8,7 @@ #include #include #include +#include #include "defines.h" #include "utilities.h" @@ -247,6 +249,58 @@ QString Utils::certUtilPath() #endif } +void Utils::saveFile(const QString &fileExtension, + const QString &caption, + const QString &fileName, + const QString &data) +{ + QString docDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); + QUrl fileUrl = QFileDialog::getSaveFileUrl(nullptr, + caption, + QUrl::fromLocalFile(docDir + "/" + fileName), + "*" + fileExtension); + if (fileUrl.isEmpty()) + return; + if (!fileUrl.toString().endsWith(fileExtension)) { + fileUrl = QUrl(fileUrl.toString() + fileExtension); + } + if (fileUrl.isEmpty()) + return; + + QFile save(fileUrl.toLocalFile()); + + //todo check if save successful + save.open(QIODevice::WriteOnly); + save.write(data.toUtf8()); + save.close(); + + QFileInfo fi(fileUrl.toLocalFile()); + QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); +} + +QString Utils::getFileName(QWidget *parent, + const QString &caption, + const QString &dir, + const QString &filter, + QString *selectedFilter, + QFileDialog::Options options) +{ + QString fileName + = QFileDialog::getOpenFileName(parent, caption, dir, filter, selectedFilter, options); + +#ifdef Q_OS_ANDROID + // patch for files containing spaces etc + const QString sep{"raw%3A%2F"}; + if (fileName.startsWith("content://") && fileName.contains(sep)) { + QString contentUrl = fileName.split(sep).at(0); + QString rawUrl = fileName.split(sep).at(1); + rawUrl.replace(" ", "%20"); + fileName = contentUrl + sep + rawUrl; + } +#endif + return fileName; +} + #ifdef Q_OS_WIN // Inspired from http://stackoverflow.com/a/15281070/1529139 // and http://stackoverflow.com/q/40059902/1529139 diff --git a/client/utilities.h b/client/utilities.h index aeb06865..191fa5c1 100644 --- a/client/utilities.h +++ b/client/utilities.h @@ -1,9 +1,10 @@ #ifndef UTILITIES_H #define UTILITIES_H +#include #include -#include #include +#include #ifdef Q_OS_WIN #include "Windows.h" @@ -50,6 +51,18 @@ public: static QString wireguardExecPath(); static QString certUtilPath(); + static void saveFile(const QString &fileExtension, + const QString &caption, + const QString &fileName, + const QString &data); + + static QString getFileName(QWidget *parent = nullptr, + const QString &caption = QString(), + const QString &dir = QString(), + const QString &filter = QString(), + QString *selectedFilter = nullptr, + QFileDialog::Options options = QFileDialog::Options()); + #ifdef Q_OS_WIN static bool signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent); #endif diff --git a/service/server/CMakeLists.txt b/service/server/CMakeLists.txt index 687b382a..2b3ff800 100644 --- a/service/server/CMakeLists.txt +++ b/service/server/CMakeLists.txt @@ -6,7 +6,7 @@ project(${PROJECT}) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) -find_package(Qt6 REQUIRED COMPONENTS Core Network RemoteObjects Core5Compat) +find_package(Qt6 REQUIRED COMPONENTS Core Network RemoteObjects Core5Compat Widgets) qt_standard_project_setup() set(HEADERS @@ -89,14 +89,14 @@ include_directories( ) add_executable(${PROJECT} ${SOURCES} ${HEADERS}) -target_link_libraries(${PROJECT} PRIVATE Qt6::Core Qt6::Network Qt6::RemoteObjects Qt6::Core5Compat ${LIBS}) +target_link_libraries(${PROJECT} PRIVATE Qt6::Core Qt6::Network Qt6::RemoteObjects Qt6::Core5Compat Qt6::Widgets ${LIBS}) qt_add_repc_sources(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipc_interface.rep) if(NOT IOS) qt_add_repc_sources(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipc_process_interface.rep) endif() -# deploy artifacts required to run the application to the debug build folder +# copy deploy artifacts required to run the application to the debug build folder if(WIN32) if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8") set(DEPLOY_ARTIFACT_PATH "windows/x64") From 3a264e6bafbd279605291f2b75b99ee126373a58 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 20 Jun 2023 10:25:24 +0900 Subject: [PATCH 033/278] added a drawer to change the server name and moved the display of the exported config to a separate drawer --- client/images/controls/telegram.svg | 2 +- .../ui/controllers/connectionController.cpp | 5 +- client/ui/controllers/exportController.cpp | 3 +- client/ui/controllers/installController.cpp | 4 +- client/ui/models/servers_model.cpp | 35 ++++-- client/ui/models/servers_model.h | 3 +- .../ConnectionTypeSelectionDrawer.qml | 4 +- .../qml/Components/ShareConnectionDrawer.qml | 100 ++++++++++++------ .../ui/qml/Controls2/LabelWithButtonType.qml | 76 ++++++++----- client/ui/qml/Controls2/ListViewType.qml | 3 +- client/ui/qml/Pages2/PageHome.qml | 12 +-- client/ui/qml/Pages2/PageSettings.qml | 24 ++--- client/ui/qml/Pages2/PageSettingsAbout.qml | 23 ++-- .../ui/qml/Pages2/PageSettingsApplication.qml | 13 ++- client/ui/qml/Pages2/PageSettingsBackup.qml | 2 +- .../ui/qml/Pages2/PageSettingsConnection.qml | 12 +-- client/ui/qml/Pages2/PageSettingsDns.qml | 2 +- .../ui/qml/Pages2/PageSettingsServerData.qml | 4 +- .../ui/qml/Pages2/PageSettingsServerInfo.qml | 54 +++++++++- .../ui/qml/Pages2/PageSettingsServersList.qml | 2 +- .../Pages2/PageSetupWizardConfigSource.qml | 16 +-- .../qml/Pages2/PageSetupWizardCredentials.qml | 2 +- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 2 +- .../qml/Pages2/PageSetupWizardProtocols.qml | 6 +- client/ui/qml/Pages2/PageSetupWizardStart.qml | 4 +- .../ui/qml/Pages2/PageSetupWizardTextKey.qml | 4 +- .../qml/Pages2/PageSetupWizardViewConfig.qml | 2 +- client/ui/qml/Pages2/PageShare.qml | 8 +- client/ui/qml/Pages2/PageStart.qml | 4 +- client/ui/qml/Pages2/PageTest.qml | 4 +- 30 files changed, 276 insertions(+), 159 deletions(-) diff --git a/client/images/controls/telegram.svg b/client/images/controls/telegram.svg index 7b76e506..92bb6a79 100644 --- a/client/images/controls/telegram.svg +++ b/client/images/controls/telegram.svg @@ -1,3 +1,3 @@ - + diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index fbbb67d9..dcc15958 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -32,9 +32,8 @@ ConnectionController::ConnectionController(const QSharedPointer &s void ConnectionController::openConnection() { int serverIndex = m_serversModel->getDefaultServerIndex(); - QModelIndex serverModelIndex = m_serversModel->index(serverIndex); - ServerCredentials credentials = qvariant_cast(m_serversModel->data(serverModelIndex, - ServersModel::ServersModelRoles::CredentialsRole)); + ServerCredentials credentials = qvariant_cast( + m_serversModel->data(serverIndex, ServersModel::ServersModelRoles::CredentialsRole)); DockerContainer container = m_containersModel->getDefaultContainer(); QModelIndex containerModelIndex = m_containersModel->index(container); diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index 98cbebd1..cbb5a1bb 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -43,7 +43,8 @@ void ExportController::generateFullAccessConfig() void ExportController::generateConnectionConfig() { int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); - ServerCredentials credentials = m_serversModel->getCurrentlyProcessedServerCredentials(); + ServerCredentials credentials = qvariant_cast( + m_serversModel->data(serverIndex, ServersModel::ServersModelRoles::CredentialsRole)); DockerContainer container = static_cast( m_containersModel->getCurrentlyProcessedContainerIndex()); diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 8f9e1f88..08ef69d4 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -61,7 +61,9 @@ void InstallController::installServer(DockerContainer container, QJsonObject& co void InstallController::installContainer(DockerContainer container, QJsonObject& config) { //todo check if container already installed - ServerCredentials serverCredentials = m_serversModel->getCurrentlyProcessedServerCredentials(); + int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + ServerCredentials serverCredentials = qvariant_cast( + m_serversModel->data(serverIndex, ServersModel::ServersModelRoles::CredentialsRole)); ServerController serverController(m_settings); ErrorCode errorCode = serverController.setupContainer(serverCredentials, container, config); diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 9ef87d37..f027a90d 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -19,12 +19,23 @@ bool ServersModel::setData(const QModelIndex &index, const QVariant &value, int return false; } + QJsonObject server = m_servers.at(index.row()).toObject(); + switch (role) { - case IsDefaultRole: { - m_settings->setDefaultServer(index.row()); - m_defaultServerIndex = m_settings->defaultServerIndex(); - } - default: return true; + case NameRole: { + server.insert(config_key::description, value.toString()); + m_settings->editServer(index.row(), server); + m_servers.replace(index.row(), server); + break; + } + case IsDefaultRole: { + m_settings->setDefaultServer(index.row()); + m_defaultServerIndex = m_settings->defaultServerIndex(); + break; + } + default: { + return true; + } } emit dataChanged(index, index); @@ -51,6 +62,8 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const return server.value(config_key::hostName).toString(); case CredentialsRole: return QVariant::fromValue(m_settings->serverCredentials(index.row())); + case CredentialsLoginRole: + return m_settings->serverCredentials(index.row()).userName; case IsDefaultRole: return index.row() == m_defaultServerIndex; case IsCurrentlyProcessedRole: @@ -60,6 +73,12 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const return QVariant(); } +QVariant ServersModel::data(const int index, int role) const +{ + QModelIndex modelIndex = this->index(index); + return data(modelIndex, role); +} + const int ServersModel::getDefaultServerIndex() { return m_defaultServerIndex; @@ -85,11 +104,6 @@ bool ServersModel::isDefaultServerCurrentlyProcessed() return m_defaultServerIndex == m_currenlyProcessedServerIndex; } -ServerCredentials ServersModel::getCurrentlyProcessedServerCredentials() -{ - return qvariant_cast(data(index(m_currenlyProcessedServerIndex), CredentialsRole)); -} - void ServersModel::addServer(const QJsonObject &server) { beginResetModel(); @@ -121,6 +135,7 @@ QHash ServersModel::roleNames() const { roles[NameRole] = "name"; roles[HostNameRole] = "hostName"; roles[CredentialsRole] = "credentials"; + roles[CredentialsLoginRole] = "credentialsLogin"; roles[IsDefaultRole] = "isDefault"; roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed"; return roles; diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index 6f55176e..be13d61b 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -19,6 +19,7 @@ public: NameRole = Qt::UserRole + 1, HostNameRole, CredentialsRole, + CredentialsLoginRole, IsDefaultRole, IsCurrentlyProcessedRole }; @@ -29,6 +30,7 @@ public: bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QVariant data(const int index, int role = Qt::DisplayRole) const; public slots: const int getDefaultServerIndex(); @@ -38,7 +40,6 @@ public slots: void setCurrentlyProcessedServerIndex(int index); int getCurrentlyProcessedServerIndex(); - ServerCredentials getCurrentlyProcessedServerCredentials(); void addServer(const QJsonObject &server); void removeServer(); diff --git a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml index 3c1ecd5b..51bffc03 100644 --- a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml +++ b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml @@ -36,7 +36,7 @@ DrawerType { Layout.topMargin: 16 text: "IP, логин и пароль от сервера" - buttonImage: "qrc:/images/controls/chevron-right.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { goToPage(PageEnum.PageSetupWizardCredentials) @@ -50,7 +50,7 @@ DrawerType { Layout.fillWidth: true text: "QR-код, ключ или файл настроек" - buttonImage: "qrc:/images/controls/chevron-right.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { goToPage(PageEnum.PageSetupWizardConfigSource) diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 1b92c752..627eba81 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -16,11 +16,12 @@ DrawerType { id: root property alias headerText: header.headerText + property alias configContentHeaderText: configContentHeader.headerText width: parent.width height: parent.height * 0.9 - Item{ + Item { anchors.fill: parent FlickableType { @@ -86,46 +87,77 @@ DrawerType { disabledColor: "#878B91" textColor: "#D7D8DB" - text: showContent ? qsTr("Collapse content") : qsTr("Show content") + text: qsTr("Show content") onClicked: { - showContent = !showContent + configContentDrawer.visible = true } } - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: configContent.implicitHeight + configContent.anchors.topMargin + configContent.anchors.bottomMargin + DrawerType { + id: configContentDrawer - radius: 10 - color: "#2C2D30" + width: parent.width + height: parent.height * 0.9 - visible: showContent + BackButtonType { + id: backButton - height: 24 + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + anchors.topMargin: 16 - TextField { - id: configContent + backButtonFunction: function() { + configContentDrawer.visible = false + } + } - anchors.fill: parent - anchors.margins: 16 + FlickableType { + anchors.top: backButton.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + contentHeight: configContent.implicitHeight + configContent.anchors.topMargin + configContent.anchors.bottomMargin - height: 24 + ColumnLayout { + id: configContent - color: "#D7D8DB" - - font.pixelSize: 16 - font.weight: Font.Medium - font.family: "PT Root UI VF" - - text: ExportController.amneziaCode - - wrapMode: Text.Wrap - - enabled: false - background: Rectangle { anchors.fill: parent - color: "transparent" + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + Header2Type { + id: configContentHeader + Layout.fillWidth: true + Layout.topMargin: 16 + } + + TextField { + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.bottomMargin: 16 + + padding: 0 + height: 24 + + color: "#D7D8DB" + + font.pixelSize: 16 + font.weight: Font.Medium + font.family: "PT Root UI VF" + + text: ExportController.amneziaCode + + wrapMode: Text.Wrap + + readOnly: true + background: Rectangle { + color: "transparent" + } + } } } } @@ -143,17 +175,19 @@ DrawerType { anchors.fill: parent smooth: false + source: ExportController.qrCodesCount ? ExportController.qrCodes[0] : "" + Timer { - property int idx: 0 + property int index: 0 interval: 1000 running: ExportController.qrCodesCount > 0 repeat: true onTriggered: { - idx++ - if (idx >= ExportController.qrCodesCount) { - idx = 0 + index++ + if (index >= ExportController.qrCodesCount) { + index = 0 } - parent.source = ExportController.qrCodes[idx] + parent.source = ExportController.qrCodes[index] } } diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml index e4e711a6..27ead16c 100644 --- a/client/ui/qml/Controls2/LabelWithButtonType.qml +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -12,8 +12,8 @@ Item { property var clickedFunction - property alias buttonImage: button.image - property string iconImage + property string rightImageSource + property string leftImageSource property string textColor: "#d7d8db" @@ -26,17 +26,34 @@ Item { anchors.leftMargin: 16 anchors.rightMargin: 16 - Image { - id: icon - source: iconImage - visible: iconImage ? true : false - Layout.rightMargin: visible ? 16 : 0 + Rectangle { + id: leftImageBackground + + visible: leftImageSource ? true : false + + Layout.preferredHeight: rightImageSource ? leftImage.implicitHeight : 56 + Layout.preferredWidth: rightImageSource ? leftImage.implicitWidth : 56 + Layout.rightMargin: rightImageSource ? 16 : 0 + + radius: 12 + color: "transparent" + + Behavior on color { + PropertyAnimation { duration: 200 } + } + + Image { + id: leftImage + + anchors.centerIn: parent + source: leftImageSource + } } ColumnLayout { ListItemTitleType { text: root.text - color: textColor + color: root.textColor Layout.fillWidth: true Layout.topMargin: 16 @@ -63,25 +80,20 @@ Item { } ImageButtonType { - id: button + id: rightImage hoverEnabled: false - image: buttonImage - onClicked: { - if (clickedFunction && typeof clickedFunction === "function") { - clickedFunction() - } - } + image: rightImageSource + visible: rightImageSource ? true : false Layout.alignment: Qt.AlignRight Rectangle { - id: imageBackground - anchors.fill: button + id: rightImageBackground + anchors.fill: parent radius: 12 color: "transparent" - Behavior on color { PropertyAnimation { duration: 200 } } @@ -106,31 +118,39 @@ Item { hoverEnabled: true onEntered: { - if (buttonImage) { - imageBackground.color = button.hoveredColor + if (rightImageSource) { + rightImageBackground.color = rightImage.hoveredColor + } else if (leftImageSource) { + leftImageBackground.color = rightImage.hoveredColor } else { - background.color = button.hoveredColor + background.color = rightImage.hoveredColor } } onExited: { - if (buttonImage) { - imageBackground.color = button.defaultColor + if (rightImageSource) { + rightImageBackground.color = rightImage.defaultColor + } else if (leftImageSource) { + leftImageBackground.color = rightImage.defaultColor } else { - background.color = button.defaultColor + background.color = rightImage.defaultColor } } onPressedChanged: { - if (buttonImage) { - imageBackground.color = pressed ? button.pressedColor : entered ? button.hoveredColor : button.defaultColor + if (rightImageSource) { + rightImageBackground.color = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor + } else if (leftImageSource) { + leftImageBackground.color = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor } else { - background.color = pressed ? button.pressedColor : entered ? button.hoveredColor : button.defaultColor + background.color = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor } } onClicked: { - button.clicked() + if (clickedFunction && typeof clickedFunction === "function") { + clickedFunction() + } } } } diff --git a/client/ui/qml/Controls2/ListViewType.qml b/client/ui/qml/Controls2/ListViewType.qml index 421b82a3..4251f0fd 100644 --- a/client/ui/qml/Controls2/ListViewType.qml +++ b/client/ui/qml/Controls2/ListViewType.qml @@ -31,7 +31,6 @@ ListView { id: content anchors.fill: parent - spacing: 16 RadioButton { id: radioButton @@ -92,7 +91,7 @@ ListView { DividerType { Layout.fillWidth: true - Layout.bottomMargin: 16 + Layout.bottomMargin: 4 visible: dividerVisible } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 9571b1fb..282c1408 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -51,7 +51,6 @@ PageType { Rectangle { id: buttonBackground anchors.fill: buttonContent - anchors.bottomMargin: -radius radius: 16 color: root.defaultColor @@ -59,11 +58,12 @@ PageType { border.width: 1 Rectangle { - width: parent.width - height: 1 - y: parent.height - height - parent.radius - - color: root.borderColor + width: parent.radius + height: parent.radius + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.left: parent.left + color: parent.color } } diff --git a/client/ui/qml/Pages2/PageSettings.qml b/client/ui/qml/Pages2/PageSettings.qml index a7472580..f430a004 100644 --- a/client/ui/qml/Pages2/PageSettings.qml +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -15,8 +15,8 @@ PageType { FlickableType { id: fl - anchors.top: root.top - anchors.bottom: root.bottom + anchors.top: parent.top + anchors.bottom: parent.bottom contentHeight: content.height ColumnLayout { @@ -40,8 +40,8 @@ PageType { Layout.topMargin: 16 text: qsTr("Servers") - buttonImage: "qrc:/images/controls/chevron-right.svg" - iconImage: "qrc:/images/controls/server.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" + leftImageSource: "qrc:/images/controls/server.svg" clickedFunction: function() { goToPage(PageEnum.PageSettingsServersList) @@ -54,8 +54,8 @@ PageType { Layout.fillWidth: true text: qsTr("Connection") - buttonImage: "qrc:/images/controls/chevron-right.svg" - iconImage: "qrc:/images/controls/radio.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" + leftImageSource: "qrc:/images/controls/radio.svg" clickedFunction: function() { goToPage(PageEnum.PageSettingsConnection) @@ -68,8 +68,8 @@ PageType { Layout.fillWidth: true text: qsTr("Application") - buttonImage: "qrc:/images/controls/chevron-right.svg" - iconImage: "qrc:/images/controls/app.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" + leftImageSource: "qrc:/images/controls/app.svg" clickedFunction: function() { goToPage(PageEnum.PageSettingsApplication) @@ -82,8 +82,8 @@ PageType { Layout.fillWidth: true text: qsTr("Backup") - buttonImage: "qrc:/images/controls/chevron-right.svg" - iconImage: "qrc:/images/controls/save.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" + leftImageSource: "qrc:/images/controls/save.svg" clickedFunction: function() { goToPage(PageEnum.PageSettingsBackup) @@ -96,8 +96,8 @@ PageType { Layout.fillWidth: true text: qsTr("About AmneziaVPN") - buttonImage: "qrc:/images/controls/chevron-right.svg" - iconImage: "qrc:/images/controls/amnezia.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" + leftImageSource: "qrc:/images/controls/amnezia.svg" clickedFunction: function() { goToPage(PageEnum.PageSettingsAbout) diff --git a/client/ui/qml/Pages2/PageSettingsAbout.qml b/client/ui/qml/Pages2/PageSettingsAbout.qml index 88c927b9..a1bdeb26 100644 --- a/client/ui/qml/Pages2/PageSettingsAbout.qml +++ b/client/ui/qml/Pages2/PageSettingsAbout.qml @@ -27,7 +27,7 @@ PageType { FlickableType { id: fl anchors.top: backButton.bottom - anchors.bottom: root.bottom + anchors.bottom: parent.bottom contentHeight: content.height ColumnLayout { @@ -102,8 +102,7 @@ And if you don't like the app, all the more support it - the donation will be us text: qsTr("Show other methods on Github") - onClicked: { - } + onClicked: Qt.openUrlExternally("https://github.com/amnezia-vpn/amnezia-client") } ParagraphTextType { @@ -121,11 +120,10 @@ And if you don't like the app, all the more support it - the donation will be us text: qsTr("Telegram group") descriptionText: qsTr("To discuss features") - buttonImage: "qrc:/images/controls/chevron-right.svg" - iconImage: "qrc:/images/controls/telegram.svg" + leftImageSource: "qrc:/images/controls/telegram.svg" clickedFunction: function() { - goToPage(PageEnum.PageSettingsAbout) + Qt.openUrlExternally("https://t.me/amnezia_vpn_dev") } } @@ -136,11 +134,9 @@ And if you don't like the app, all the more support it - the donation will be us text: qsTr("Mail") descriptionText: qsTr("For reviews and bug reports") - buttonImage: "qrc:/images/controls/chevron-right.svg" - iconImage: "qrc:/images/controls/mail.svg" + leftImageSource: "qrc:/images/controls/mail.svg" clickedFunction: function() { - goToPage(PageEnum.PageSettingsAbout) } } @@ -150,11 +146,10 @@ And if you don't like the app, all the more support it - the donation will be us Layout.fillWidth: true text: qsTr("Github") - buttonImage: "qrc:/images/controls/chevron-right.svg" - iconImage: "qrc:/images/controls/github.svg" + leftImageSource: "qrc:/images/controls/github.svg" clickedFunction: function() { - goToPage(PageEnum.PageSettingsAbout) + Qt.openUrlExternally("https://github.com/amnezia-vpn/amnezia-client") } } @@ -164,11 +159,9 @@ And if you don't like the app, all the more support it - the donation will be us Layout.fillWidth: true text: qsTr("Website") - buttonImage: "qrc:/images/controls/chevron-right.svg" - iconImage: "qrc:/images/controls/amnezia.svg" + leftImageSource: "qrc:/images/controls/amnezia.svg" clickedFunction: function() { - goToPage(PageEnum.PageSettingsAbout) } } diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index 9a6eab3c..08a5ec0d 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -26,7 +26,7 @@ PageType { FlickableType { id: fl anchors.top: backButton.bottom - anchors.bottom: root.bottom + anchors.bottom: parent.bottom contentHeight: content.height ColumnLayout { @@ -35,22 +35,21 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.leftMargin: 16 - anchors.rightMargin: 16 - - spacing: 16 HeaderType { Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 headerText: qsTr("Application") } LabelWithButtonType { Layout.fillWidth: true + Layout.topMargin: 16 text: qsTr("Language") - buttonImage: "qrc:/images/controls/chevron-right.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { } @@ -62,7 +61,7 @@ PageType { Layout.fillWidth: true text: qsTr("Reset settings and remove all data from the application") - buttonImage: "qrc:/images/controls/chevron-right.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { } diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml index 0cc62979..a5445754 100644 --- a/client/ui/qml/Pages2/PageSettingsBackup.qml +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -26,7 +26,7 @@ PageType { FlickableType { id: fl anchors.top: backButton.bottom - anchors.bottom: root.bottom + anchors.bottom: parent.bottom contentHeight: content.height ColumnLayout { diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml index fb0bb000..ad8524f5 100644 --- a/client/ui/qml/Pages2/PageSettingsConnection.qml +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -25,7 +25,7 @@ PageType { FlickableType { id: fl anchors.top: backButton.bottom - anchors.bottom: root.bottom + anchors.bottom: parent.bottom contentHeight: content.height ColumnLayout { @@ -35,8 +35,6 @@ PageType { anchors.left: parent.left anchors.right: parent.right - spacing: 16 - HeaderType { Layout.fillWidth: true Layout.leftMargin: 16 @@ -47,6 +45,8 @@ PageType { SwitcherType { Layout.fillWidth: true + Layout.topMargin: 16 + Layout.bottomMargin: 16 Layout.leftMargin: 16 Layout.rightMargin: 16 @@ -68,7 +68,7 @@ PageType { text: qsTr("DNS servers") descriptionText: qsTr("If AmneziaDNS is not used or installed") - buttonImage: "qrc:/images/controls/chevron-right.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { goToPage(PageEnum.PageSettingsDns) @@ -82,7 +82,7 @@ PageType { text: qsTr("Split site tunneling") descriptionText: qsTr("Allows you to connect to some sites through a secure connection, and to others bypassing it") - buttonImage: "qrc:/images/controls/chevron-right.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { } @@ -95,7 +95,7 @@ PageType { text: qsTr("Separate application tunneling") descriptionText: qsTr("Allows you to use the VPN only for certain applications") - buttonImage: "qrc:/images/controls/chevron-right.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { } diff --git a/client/ui/qml/Pages2/PageSettingsDns.qml b/client/ui/qml/Pages2/PageSettingsDns.qml index 2a06438b..1c989c0c 100644 --- a/client/ui/qml/Pages2/PageSettingsDns.qml +++ b/client/ui/qml/Pages2/PageSettingsDns.qml @@ -26,7 +26,7 @@ PageType { FlickableType { id: fl anchors.top: backButton.bottom - anchors.bottom: root.bottom + anchors.bottom: parent.bottom contentHeight: content.height ColumnLayout { diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index fe09ed01..6179983b 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -16,8 +16,8 @@ PageType { FlickableType { id: fl - anchors.top: root.top - anchors.bottom: root.bottom + anchors.top: parent.top + anchors.bottom: parent.bottom contentHeight: content.height ColumnLayout { diff --git a/client/ui/qml/Pages2/PageSettingsServerInfo.qml b/client/ui/qml/Pages2/PageSettingsServerInfo.qml index 5e422230..3f2562da 100644 --- a/client/ui/qml/Pages2/PageSettingsServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsServerInfo.qml @@ -54,10 +54,60 @@ PageType { actionButtonImage: "qrc:/images/controls/edit-3.svg" headerText: name - descriptionText: hostName + descriptionText: credentialsLogin + " · " + hostName actionButtonFunction: function() { - connectionTypeSelection.visible = true + serverNameEditDrawer.visible = true + } + } + + DrawerType { + id: serverNameEditDrawer + + width: root.width + height: root.height * 0.35 + + onVisibleChanged: { + if (serverNameEditDrawer.visible) { + serverName.textField.forceActiveFocus() + } + } + + ColumnLayout { + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + TextFieldWithHeaderType { + id: serverName + + Layout.fillWidth: true + headerText: qsTr("Server name") + textFieldText: name + } + + BasicButtonType { + Layout.fillWidth: true + + 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("Save") + + onClicked: { + if (serverName.textFieldText !== name) { + name = serverName.textFieldText + serverNameEditDrawer.visible = false + } + } + } } } } diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index fa4ce86c..a601199a 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -86,7 +86,7 @@ PageType { text: name descriptionText: hostName - buttonImage: "qrc:/images/controls/chevron-right.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { ServersModel.setCurrentlyProcessedServerIndex(index) diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 79e596af..2ef38067 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -15,8 +15,8 @@ PageType { FlickableType { id: fl - anchors.top: root.top - anchors.bottom: root.bottom + anchors.top: parent.top + anchors.bottom: parent.bottom contentHeight: content.height ColumnLayout { @@ -58,8 +58,8 @@ PageType { Layout.topMargin: 16 text: "Файл с настройками подключения" - buttonImage: "qrc:/images/controls/chevron-right.svg" - iconImage: "qrc:/images/controls/folder-open.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" + leftImageSource: "qrc:/images/controls/folder-open.svg" clickedFunction: function() { onClicked: fileDialog.open() @@ -81,8 +81,8 @@ PageType { Layout.fillWidth: true text: "QR-код" - buttonImage: "qrc:/images/controls/chevron-right.svg" - iconImage: "qrc:/images/controls/qr-code.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" + leftImageSource: "qrc:/images/controls/qr-code.svg" clickedFunction: function() { } @@ -94,8 +94,8 @@ PageType { Layout.fillWidth: true text: "Ключ в виде текста" - buttonImage: "qrc:/images/controls/chevron-right.svg" - iconImage: "qrc:/images/controls/text-cursor.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" + leftImageSource: "qrc:/images/controls/text-cursor.svg" clickedFunction: function() { goToPage(PageEnum.PageSetupWizardTextKey) diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index 49197962..3f037035 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -25,7 +25,7 @@ PageType { FlickableType { id: fl anchors.top: backButton.bottom - anchors.bottom: root.bottom + anchors.bottom: parent.bottom contentHeight: content.height ColumnLayout { diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index 1836f892..19f06d5f 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -44,7 +44,7 @@ PageType { FlickableType { id: fl anchors.top: backButton.bottom - anchors.bottom: root.bottom + anchors.bottom: parent.bottom contentHeight: content.implicitHeight + continueButton.anchors.bottomMargin Column { diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index e2ae4a16..527df830 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -32,8 +32,8 @@ PageType { FlickableType { id: fl - anchors.top: root.top - anchors.bottom: root.bottom + anchors.top: parent.top + anchors.bottom: parent.bottom contentHeight: content.height Column { @@ -87,7 +87,7 @@ PageType { text: name descriptionText: description - buttonImage: "qrc:/images/controls/chevron-right.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(index)) diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index 6358f00d..ad91303e 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -24,8 +24,8 @@ PageType { FlickableType { id: fl - anchors.top: root.top - anchors.bottom: root.bottom + anchors.top: parent.top + anchors.bottom: parent.bottom contentHeight: content.height ColumnLayout { diff --git a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml index f74c3733..d7dbf43d 100644 --- a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml +++ b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml @@ -14,8 +14,8 @@ PageType { FlickableType { id: fl - anchors.top: root.top - anchors.bottom: root.bottom + anchors.top: parent.top + anchors.bottom: parent.bottom contentHeight: content.height ColumnLayout { diff --git a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml index ca679e79..c7b774b4 100644 --- a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml +++ b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml @@ -50,7 +50,7 @@ PageType { FlickableType { id: fl anchors.top: backButton.bottom - anchors.bottom: root.bottom + anchors.bottom: parent.bottom contentHeight: content.implicitHeight + connectButton.implicitHeight ColumnLayout { diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 64641094..504b0cb1 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -56,8 +56,8 @@ PageType { } FlickableType { - anchors.top: root.top - anchors.bottom: root.bottom + anchors.top: parent.top + anchors.bottom: parent.bottom contentHeight: content.height ColumnLayout { @@ -121,6 +121,7 @@ PageType { ParagraphTextType { Layout.fillWidth: true + Layout.topMargin: 24 text: accessTypeSelector.currentIndex === 0 ? qsTr("VPN access without the ability to manage the server") : qsTr("Full access to server") @@ -222,7 +223,6 @@ PageType { roleName: "isInstalled" value: true } - ] } @@ -231,6 +231,7 @@ PageType { clickedFunction: function () { serverSelector.text += ", " + selectedText shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text + shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(currentIndex)) protocolSelector.visible = false @@ -242,6 +243,7 @@ PageType { Component.onCompleted: { serverSelector.text += ", " + selectedText shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text + shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(currentIndex)) fillConnectionTypeModel() diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 8b9cefa4..a91cab6a 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -59,11 +59,13 @@ PageType { anchors.bottom: parent.bottom topPadding: 8 - bottomPadding: 34 + bottomPadding: 8//34 leftPadding: 96 rightPadding: 96 background: Rectangle { + border.width: 1 + border.color: "#2C2D30" color: "#1C1D21" } diff --git a/client/ui/qml/Pages2/PageTest.qml b/client/ui/qml/Pages2/PageTest.qml index 53ec99fc..d33608f8 100644 --- a/client/ui/qml/Pages2/PageTest.qml +++ b/client/ui/qml/Pages2/PageTest.qml @@ -122,7 +122,7 @@ Item { Layout.rightMargin: 16 text: "IP, логин и пароль от сервера" - buttonImage: "qrc:/images/controls/chevron-right.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" } Rectangle { @@ -139,7 +139,7 @@ Item { Layout.rightMargin: 16 text: "QR-код, ключ или файл настроек" - buttonImage: "qrc:/images/controls/chevron-right.svg" + rightImageSource: "qrc:/images/controls/chevron-right.svg" } Rectangle { From 249be451f75bfb69778992b742feb23b0f326120 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 21 Jun 2023 20:56:00 +0900 Subject: [PATCH 034/278] moved handling of connection states from qml in connectionController - added a check for already installed containers before installing the server/container - added a button to scan the server for installed containers - added separation for read/write and readonly servers for pageHome --- client/resources.qrc | 1 + .../ui/controllers/connectionController.cpp | 98 ++++++++---- client/ui/controllers/connectionController.h | 16 +- client/ui/controllers/exportController.cpp | 2 +- client/ui/controllers/importController.cpp | 2 +- client/ui/controllers/installController.cpp | 150 ++++++++++++++---- client/ui/controllers/installController.h | 28 ++-- client/ui/controllers/pageController.cpp | 2 +- client/ui/controllers/pageController.h | 16 +- client/ui/models/containers_model.cpp | 90 +++++------ client/ui/models/servers_model.cpp | 46 +++--- client/ui/models/servers_model.h | 11 +- client/ui/qml/Components/ConnectButton.qml | 79 +++------ .../qml/Components/ShareConnectionDrawer.qml | 1 + .../qml/Controls2/TextFieldWithHeaderType.qml | 9 +- .../qml/Controls2/TextTypes/SmallTextType.qml | 12 ++ client/ui/qml/Pages2/PageHome.qml | 71 +++++++-- .../ui/qml/Pages2/PageSettingsServerData.qml | 32 +++- .../qml/Pages2/PageSetupWizardCredentials.qml | 17 +- .../qml/Pages2/PageSetupWizardInstalling.qml | 23 ++- client/ui/qml/Pages2/PageStart.qml | 5 + 21 files changed, 466 insertions(+), 245 deletions(-) create mode 100644 client/ui/qml/Controls2/TextTypes/SmallTextType.qml diff --git a/client/resources.qrc b/client/resources.qrc index a8f8718e..5b5fd593 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -268,5 +268,6 @@ images/controls/github.svg images/controls/mail.svg images/controls/telegram.svg + ui/qml/Controls2/TextTypes/SmallTextType.qml diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index dcc15958..aace2857 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -6,39 +6,27 @@ ConnectionController::ConnectionController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, - const QSharedPointer &vpnConnection, - QObject *parent) - : QObject(parent) - , m_serversModel(serversModel) - , m_containersModel(containersModel) - , m_vpnConnection(vpnConnection) + const QSharedPointer &vpnConnection, QObject *parent) + : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_vpnConnection(vpnConnection) { - connect(m_vpnConnection.get(), - &VpnConnection::connectionStateChanged, - this, - &ConnectionController::connectionStateChanged); - connect(this, - &ConnectionController::connectToVpn, - m_vpnConnection.get(), - &VpnConnection::connectToVpn, + connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, this, + &ConnectionController::onConnectionStateChanged); + connect(this, &ConnectionController::connectToVpn, m_vpnConnection.get(), &VpnConnection::connectToVpn, Qt::QueuedConnection); - connect(this, - &ConnectionController::disconnectFromVpn, - m_vpnConnection.get(), - &VpnConnection::disconnectFromVpn, + connect(this, &ConnectionController::disconnectFromVpn, m_vpnConnection.get(), &VpnConnection::disconnectFromVpn, Qt::QueuedConnection); } void ConnectionController::openConnection() { int serverIndex = m_serversModel->getDefaultServerIndex(); - ServerCredentials credentials = qvariant_cast( - m_serversModel->data(serverIndex, ServersModel::ServersModelRoles::CredentialsRole)); + ServerCredentials credentials = + qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); DockerContainer container = m_containersModel->getDefaultContainer(); QModelIndex containerModelIndex = m_containersModel->index(container); - const QJsonObject &containerConfig = qvariant_cast(m_containersModel->data(containerModelIndex, - ContainersModel::Roles::ConfigRole)); + const QJsonObject &containerConfig = + qvariant_cast(m_containersModel->data(containerModelIndex, ContainersModel::Roles::ConfigRole)); if (container == DockerContainer::None) { emit connectionErrorOccurred(tr("VPN Protocols is not installed.\n Please install VPN container at first")); @@ -59,13 +47,65 @@ QString ConnectionController::getLastConnectionError() return errorString(m_vpnConnection->lastError()); } -bool ConnectionController::isConnected() +void ConnectionController::onConnectionStateChanged(Vpn::ConnectionState state) +{ + m_isConnected = false; + m_connectionStateText = tr("Connection..."); + switch (state) { + case Vpn::ConnectionState::Connected: { + m_isConnectionInProgress = false; + m_isConnected = true; + m_connectionStateText = tr("Disconnect"); + break; + } + case Vpn::ConnectionState::Connecting: { + m_isConnectionInProgress = true; + break; + } + case Vpn::ConnectionState::Reconnecting: { + m_isConnectionInProgress = true; + m_connectionStateText = tr("Reconnection..."); + break; + } + case Vpn::ConnectionState::Disconnected: { + m_isConnectionInProgress = false; + m_connectionStateText = tr("Connect"); + break; + } + case Vpn::ConnectionState::Disconnecting: { + m_isConnectionInProgress = true; + m_connectionStateText = tr("Disconnection..."); + break; + } + case Vpn::ConnectionState::Preparing: { + m_isConnectionInProgress = true; + break; + } + case Vpn::ConnectionState::Error: { + m_isConnectionInProgress = false; + emit connectionErrorOccurred(getLastConnectionError()); + break; + } + case Vpn::ConnectionState::Unknown: { + m_isConnectionInProgress = false; + emit connectionErrorOccurred(getLastConnectionError()); + break; + } + } + emit connectionStateChanged(); +} + +QString ConnectionController::connectionStateText() const +{ + return m_connectionStateText; +} + +bool ConnectionController::isConnectionInProgress() const +{ + return m_isConnectionInProgress; +} + +bool ConnectionController::isConnected() const { return m_isConnected; } - -void ConnectionController::setIsConnected(bool isConnected) -{ - m_isConnected = isConnected; - emit isConnectedChanged(); -} diff --git a/client/ui/controllers/connectionController.h b/client/ui/controllers/connectionController.h index c1e81ea3..421ae84f 100644 --- a/client/ui/controllers/connectionController.h +++ b/client/ui/controllers/connectionController.h @@ -11,28 +11,30 @@ class ConnectionController : public QObject Q_OBJECT public: - Q_PROPERTY(bool isConnected READ isConnected WRITE setIsConnected NOTIFY isConnectedChanged) + Q_PROPERTY(bool isConnected READ isConnected NOTIFY connectionStateChanged) + Q_PROPERTY(bool isConnectionInProgress READ isConnectionInProgress NOTIFY connectionStateChanged) + Q_PROPERTY(QString connectionStateText READ connectionStateText NOTIFY connectionStateChanged) explicit ConnectionController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, const QSharedPointer &vpnConnection, QObject *parent = nullptr); - bool isConnected(); - void setIsConnected(bool isConnected); //todo take state from vpnconnection? + bool isConnected() const; + bool isConnectionInProgress() const; + QString connectionStateText() const; public slots: void openConnection(); void closeConnection(); QString getLastConnectionError(); - Vpn::ConnectionState connectionState(){return {};}; //todo update ConnectButton text on page change + void onConnectionStateChanged(Vpn::ConnectionState state); signals: void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig); void disconnectFromVpn(); - void connectionStateChanged(Vpn::ConnectionState state); - void isConnectedChanged(); + void connectionStateChanged(); void connectionErrorOccurred(QString errorMessage); @@ -43,6 +45,8 @@ private: QSharedPointer m_vpnConnection; bool m_isConnected = false; + bool m_isConnectionInProgress = false; + QString m_connectionStateText = tr("Connect"); }; #endif // CONNECTIONCONTROLLER_H diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index cbb5a1bb..b6cd7abb 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -44,7 +44,7 @@ void ExportController::generateConnectionConfig() { int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); ServerCredentials credentials = qvariant_cast( - m_serversModel->data(serverIndex, ServersModel::ServersModelRoles::CredentialsRole)); + m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); DockerContainer container = static_cast( m_containersModel->getCurrentlyProcessedContainerIndex()); diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 2724f9cd..99de3131 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -89,7 +89,7 @@ void ImportController::importConfig() if (!m_config.value(config_key::containers).toArray().isEmpty()) { auto newServerIndex = m_serversModel->index(m_serversModel->getServersCount() - 1); - m_serversModel->setData(newServerIndex, true, ServersModel::ServersModelRoles::IsDefaultRole); + m_serversModel->setData(newServerIndex, true, ServersModel::Roles::IsDefaultRole); } emit importFinished(); diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 08ef69d4..5d3bfc2f 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -2,40 +2,54 @@ #include -#include "core/servercontroller.h" #include "core/errorstrings.h" +#include "core/servercontroller.h" +#include "utilities.h" InstallController::InstallController(const QSharedPointer &serversModel, - const QSharedPointer &containersModel, - const std::shared_ptr &settings, - QObject *parent) : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings) -{} + const QSharedPointer &containersModel, + const std::shared_ptr &settings, QObject *parent) + : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings) +{ +} void InstallController::install(DockerContainer container, int port, TransportProto transportProto) { Proto mainProto = ContainerProps::defaultProtocol(container); - QJsonObject containerConfig { - { config_key::port, QString::number(port) }, - { config_key::transport_proto, ProtocolProps::transportProtoToString(transportProto, mainProto) } - }; - QJsonObject config { - { config_key::container, ContainerProps::containerToString(container) }, - { ProtocolProps::protoToString(mainProto), containerConfig } - }; + QJsonObject containerConfig { { config_key::port, QString::number(port) }, + { config_key::transport_proto, + ProtocolProps::transportProtoToString(transportProto, mainProto) } }; + QJsonObject config { { config_key::container, ContainerProps::containerToString(container) }, + { ProtocolProps::protoToString(mainProto), containerConfig } }; if (m_shouldCreateServer) { + if (isServerAlreadyExists()) { + return; + } installServer(container, config); } else { installContainer(container, config); } } -void InstallController::installServer(DockerContainer container, QJsonObject& config) +void InstallController::installServer(DockerContainer container, QJsonObject &config) { - //todo check if container already installed ServerController serverController(m_settings); - ErrorCode errorCode = serverController.setupContainer(m_currentlyInstalledServerCredentials, container, config); + + QMap installedContainers; + ErrorCode errorCode = + serverController.getAlreadyInstalledContainers(m_currentlyInstalledServerCredentials, installedContainers); + if (!installedContainers.contains(container)) { + errorCode = serverController.setupContainer(m_currentlyInstalledServerCredentials, container, config); + installedContainers.insert(container, config); + } + + bool isInstalledContainerFound = false; + if (!installedContainers.isEmpty()) { + isInstalledContainerFound = true; + } + if (errorCode == ErrorCode::NoError) { QJsonObject server; server.insert(config_key::hostName, m_currentlyInstalledServerCredentials.hostName); @@ -44,43 +58,123 @@ void InstallController::installServer(DockerContainer container, QJsonObject& co server.insert(config_key::port, m_currentlyInstalledServerCredentials.port); server.insert(config_key::description, m_settings->nextAvailableServerName()); - server.insert(config_key::containers, QJsonArray{ config }); + QJsonArray containerConfigs; + for (const QJsonObject &containerConfig : qAsConst(installedContainers)) { + containerConfigs.append(containerConfig); + } + + server.insert(config_key::containers, containerConfigs); server.insert(config_key::defaultContainer, ContainerProps::containerToString(container)); m_serversModel->addServer(server); auto newServerIndex = m_serversModel->index(m_serversModel->getServersCount() - 1); - m_serversModel->setData(newServerIndex, true, ServersModel::ServersModelRoles::IsDefaultRole); + m_serversModel->setData(newServerIndex, true, ServersModel::Roles::IsDefaultRole); - emit installServerFinished(); + emit installServerFinished(isInstalledContainerFound); return; } emit installationErrorOccurred(errorString(errorCode)); } -void InstallController::installContainer(DockerContainer container, QJsonObject& config) +void InstallController::installContainer(DockerContainer container, QJsonObject &config) { - //todo check if container already installed int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); - ServerCredentials serverCredentials = qvariant_cast( - m_serversModel->data(serverIndex, ServersModel::ServersModelRoles::CredentialsRole)); + ServerCredentials serverCredentials = + qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); ServerController serverController(m_settings); - ErrorCode errorCode = serverController.setupContainer(serverCredentials, container, config); + + QMap installedContainers; + ErrorCode errorCode = serverController.getAlreadyInstalledContainers(serverCredentials, installedContainers); + + bool isInstalledContainerFound = false; + if (!installedContainers.isEmpty()) { + isInstalledContainerFound = true; + } + + if (!installedContainers.contains(container)) { + errorCode = serverController.setupContainer(serverCredentials, container, config); + installedContainers.insert(container, config); + } + if (errorCode == ErrorCode::NoError) { - m_containersModel->setData(m_containersModel->index(container), config, ContainersModel::Roles::ConfigRole); - emit installContainerFinished(); + for (auto iterator = installedContainers.begin(); iterator != installedContainers.end(); iterator++) { + auto modelIndex = m_containersModel->index(iterator.key()); + QJsonObject containerConfig = + qvariant_cast(m_containersModel->data(modelIndex, ContainersModel::Roles::ConfigRole)); + if (containerConfig.isEmpty()) { + m_containersModel->setData(m_containersModel->index(iterator.key()), iterator.value(), + ContainersModel::Roles::ConfigRole); + } + } + + emit installContainerFinished(isInstalledContainerFound); return; } emit installationErrorOccurred(errorString(errorCode)); } -void InstallController::setCurrentlyInstalledServerCredentials(const QString &hostName, const QString &userName, const QString &secretData) +bool InstallController::isServerAlreadyExists() +{ + for (int i = 0; i < m_serversModel->getServersCount(); i++) { + auto modelIndex = m_serversModel->index(i); + const ServerCredentials credentials = + qvariant_cast(m_serversModel->data(modelIndex, ServersModel::Roles::CredentialsRole)); + if (m_currentlyInstalledServerCredentials.hostName == credentials.hostName + && m_currentlyInstalledServerCredentials.port == credentials.port) { + emit serverAlreadyExists(i); + return true; + } + } + return false; +} + +void InstallController::scanServerForInstalledContainers() +{ + int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + ServerCredentials serverCredentials = + qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); + + ServerController serverController(m_settings); + + QMap installedContainers; + ErrorCode errorCode = serverController.getAlreadyInstalledContainers(serverCredentials, installedContainers); + + if (errorCode == ErrorCode::NoError) { + bool isInstalledContainerAddedToGui = false; + + for (auto iterator = installedContainers.begin(); iterator != installedContainers.end(); iterator++) { + auto modelIndex = m_containersModel->index(iterator.key()); + QJsonObject containerConfig = + qvariant_cast(m_containersModel->data(modelIndex, ContainersModel::Roles::ConfigRole)); + if (containerConfig.isEmpty()) { + m_containersModel->setData(m_containersModel->index(iterator.key()), iterator.value(), + ContainersModel::Roles::ConfigRole); + isInstalledContainerAddedToGui = true; + } + } + + emit scanServerFinished(isInstalledContainerAddedToGui); + return; + } + + emit installationErrorOccurred(errorString(errorCode)); +} + +QRegularExpression InstallController::ipAddressPortRegExp() +{ + return Utils::ipAddressPortRegExp(); +} + +void InstallController::setCurrentlyInstalledServerCredentials(const QString &hostName, const QString &userName, + const QString &secretData) { m_currentlyInstalledServerCredentials.hostName = hostName; if (m_currentlyInstalledServerCredentials.hostName.contains(":")) { - m_currentlyInstalledServerCredentials.port = m_currentlyInstalledServerCredentials.hostName.split(":").at(1).toInt(); + m_currentlyInstalledServerCredentials.port = + m_currentlyInstalledServerCredentials.hostName.split(":").at(1).toInt(); m_currentlyInstalledServerCredentials.hostName = m_currentlyInstalledServerCredentials.hostName.split(":").at(0); } m_currentlyInstalledServerCredentials.userName = userName; diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index d9e1ad95..6b01e102 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -3,10 +3,10 @@ #include -#include "core/defs.h" #include "containers/containers_defs.h" -#include "ui/models/servers_model.h" +#include "core/defs.h" #include "ui/models/containers_model.h" +#include "ui/models/servers_model.h" class InstallController : public QObject { @@ -14,22 +14,32 @@ class InstallController : public QObject public: explicit InstallController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, - const std::shared_ptr &settings, - QObject *parent = nullptr); + const std::shared_ptr &settings, QObject *parent = nullptr); public slots: void install(DockerContainer container, int port, TransportProto transportProto); - void setCurrentlyInstalledServerCredentials(const QString &hostName, const QString &userName, const QString &secretData); + void setCurrentlyInstalledServerCredentials(const QString &hostName, const QString &userName, + const QString &secretData); void setShouldCreateServer(bool shouldCreateServer); + void scanServerForInstalledContainers(); + + QRegularExpression ipAddressPortRegExp(); + signals: - void installContainerFinished(); - void installServerFinished(); + void installContainerFinished(bool isInstalledContainerFound); + void installServerFinished(bool isInstalledContainerFound); + + void scanServerFinished(bool isInstalledContainerFound); void installationErrorOccurred(QString errorMessage); + + void serverAlreadyExists(int serverIndex); + private: - void installServer(DockerContainer container, QJsonObject& config); - void installContainer(DockerContainer container, QJsonObject& config); + void installServer(DockerContainer container, QJsonObject &config); + void installContainer(DockerContainer container, QJsonObject &config); + bool isServerAlreadyExists(); QSharedPointer m_serversModel; QSharedPointer m_containersModel; diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/pageController.cpp index e49177a5..4ee9b3bf 100644 --- a/client/ui/controllers/pageController.cpp +++ b/client/ui/controllers/pageController.cpp @@ -10,7 +10,7 @@ QString PageController::getInitialPage() if (m_serversModel->getServersCount()) { if (m_serversModel->getDefaultServerIndex() < 0) { auto defaultServerIndex = m_serversModel->index(0); - m_serversModel->setData(defaultServerIndex, true, ServersModel::ServersModelRoles::IsDefaultRole); + m_serversModel->setData(defaultServerIndex, true, ServersModel::Roles::IsDefaultRole); } return getPagePath(PageLoader::PageEnum::PageStart); } else { diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 587e0e38..384d3c8d 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -40,14 +40,9 @@ namespace PageLoader }; Q_ENUM_NS(PageEnum) - static void declareQmlPageEnum() { - qmlRegisterUncreatableMetaObject( - PageLoader::staticMetaObject, - "PageEnum", - 1, 0, - "PageEnum", - "Error: only enums" - ); + static void declareQmlPageEnum() + { + qmlRegisterUncreatableMetaObject(PageLoader::staticMetaObject, "PageEnum", 1, 0, "PageEnum", "Error: only enums"); } } @@ -55,8 +50,7 @@ class PageController : public QObject { Q_OBJECT public: - explicit PageController(const QSharedPointer &serversModel, - QObject *parent = nullptr); + explicit PageController(const QSharedPointer &serversModel, QObject *parent = nullptr); public slots: QString getInitialPage(); @@ -64,9 +58,11 @@ public slots: signals: void goToPageHome(); + void goToPageSettings(); void restorePageHomeState(bool isContainerInstalled = false); void replaceStartPage(); void showErrorMessage(QString errorMessage); + void showInfoMessage(QString message); private: QSharedPointer m_serversModel; diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index 1caf6944..f095dd02 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -2,7 +2,8 @@ #include "core/servercontroller.h" -ContainersModel::ContainersModel(std::shared_ptr settings, QObject *parent) : m_settings(settings), QAbstractListModel(parent) +ContainersModel::ContainersModel(std::shared_ptr settings, QObject *parent) + : m_settings(settings), QAbstractListModel(parent) { } @@ -21,25 +22,25 @@ bool ContainersModel::setData(const QModelIndex &index, const QVariant &value, i DockerContainer container = ContainerProps::allContainers().at(index.row()); switch (role) { - case NameRole: - // return ContainerProps::containerHumanNames().value(container); - case DescRole: - // return ContainerProps::containerDescriptions().value(container); - case ConfigRole: //todo save to model also - m_settings->setContainerConfig(m_currentlyProcessedServerIndex, - container, - value.toJsonObject()); - case ServiceTypeRole: - // return ContainerProps::containerService(container); - case DockerContainerRole: - // return container; - case IsInstalledRole: - // return m_settings->containers(m_currentlyProcessedServerIndex).contains(container); - case IsDefaultRole: { - m_settings->setDefaultContainer(m_currentlyProcessedServerIndex, container); - m_defaultContainerIndex = container; - emit defaultContainerChanged(); - } + case NameRole: + // return ContainerProps::containerHumanNames().value(container); + case DescRole: + // return ContainerProps::containerDescriptions().value(container); + case ConfigRole: { + m_settings->setContainerConfig(m_currentlyProcessedServerIndex, container, value.toJsonObject()); + m_containers = m_settings->containers(m_currentlyProcessedServerIndex); + } + case ServiceTypeRole: + // return ContainerProps::containerService(container); + case DockerContainerRole: + // return container; + case IsInstalledRole: + // return m_settings->containers(m_currentlyProcessedServerIndex).contains(container); + case IsDefaultRole: { + m_settings->setDefaultContainer(m_currentlyProcessedServerIndex, container); + m_defaultContainerIndex = container; + emit defaultContainerChanged(); + } } emit dataChanged(index, index); @@ -48,40 +49,30 @@ bool ContainersModel::setData(const QModelIndex &index, const QVariant &value, i QVariant ContainersModel::data(const QModelIndex &index, int role) const { - if (!index.isValid() || index.row() < 0 - || index.row() >= ContainerProps::allContainers().size()) { + if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { return QVariant(); } DockerContainer container = ContainerProps::allContainers().at(index.row()); switch (role) { - case NameRole: - return ContainerProps::containerHumanNames().value(container); - case DescRole: - return ContainerProps::containerDescriptions().value(container); - case ConfigRole: { - if (container == DockerContainer::None) return QJsonObject(); - return m_containers.value(container); + case NameRole: return ContainerProps::containerHumanNames().value(container); + case DescRole: return ContainerProps::containerDescriptions().value(container); + case ConfigRole: { + if (container == DockerContainer::None) { + return QJsonObject(); } - case ServiceTypeRole: - return ContainerProps::containerService(container); - case DockerContainerRole: - return container; - case IsEasySetupContainerRole: - return ContainerProps::isEasySetupContainer(container); - case EasySetupHeaderRole: - return ContainerProps::easySetupHeader(container); - case EasySetupDescriptionRole: - return ContainerProps::easySetupDescription(container); - case IsInstalledRole: - return m_containers.contains(container); - case IsCurrentlyProcessedRole: - return container == static_cast(m_currentlyProcessedContainerIndex); - case IsDefaultRole: - return container == m_defaultContainerIndex; - case IsSupportedRole: - return ContainerProps::isSupportedByCurrentPlatform(container); + return m_containers.value(container); + } + case ServiceTypeRole: return ContainerProps::containerService(container); + case DockerContainerRole: return container; + case IsEasySetupContainerRole: return ContainerProps::isEasySetupContainer(container); + case EasySetupHeaderRole: return ContainerProps::easySetupHeader(container); + case EasySetupDescriptionRole: return ContainerProps::easySetupDescription(container); + case IsInstalledRole: return m_containers.contains(container); + case IsCurrentlyProcessedRole: return container == static_cast(m_currentlyProcessedContainerIndex); + case IsDefaultRole: return container == m_defaultContainerIndex; + case IsSupportedRole: return ContainerProps::isSupportedByCurrentPlatform(container); } return QVariant(); @@ -130,7 +121,7 @@ void ContainersModel::removeAllContainers() endResetModel(); } - //todo process errors + // todo process errors } void ContainersModel::clearCachedProfiles() @@ -141,7 +132,8 @@ void ContainersModel::clearCachedProfiles() } } -QHash ContainersModel::roleNames() const { +QHash ContainersModel::roleNames() const +{ QHash roles; roles[NameRole] = "name"; roles[DescRole] = "description"; diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index f027a90d..94267ed1 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -1,6 +1,7 @@ #include "servers_model.h" -ServersModel::ServersModel(std::shared_ptr settings, QObject *parent) : m_settings(settings), QAbstractListModel(parent) +ServersModel::ServersModel(std::shared_ptr settings, QObject *parent) + : m_settings(settings), QAbstractListModel(parent) { m_servers = m_settings->serversArray(); m_defaultServerIndex = m_settings->defaultServerIndex(); @@ -14,8 +15,7 @@ int ServersModel::rowCount(const QModelIndex &parent) const bool ServersModel::setData(const QModelIndex &index, const QVariant &value, int role) { - if (!index.isValid() || index.row() < 0 - || index.row() >= static_cast(m_servers.size())) { + if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(m_servers.size())) { return false; } @@ -29,8 +29,7 @@ bool ServersModel::setData(const QModelIndex &index, const QVariant &value, int break; } case IsDefaultRole: { - m_settings->setDefaultServer(index.row()); - m_defaultServerIndex = m_settings->defaultServerIndex(); + setDefaultServerIndex(index.row()); break; } default: { @@ -58,16 +57,11 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const } return description; } - case HostNameRole: - return server.value(config_key::hostName).toString(); - case CredentialsRole: - return QVariant::fromValue(m_settings->serverCredentials(index.row())); - case CredentialsLoginRole: - return m_settings->serverCredentials(index.row()).userName; - case IsDefaultRole: - return index.row() == m_defaultServerIndex; - case IsCurrentlyProcessedRole: - return index.row() == m_currenlyProcessedServerIndex; + case HostNameRole: return server.value(config_key::hostName).toString(); + case CredentialsRole: return QVariant::fromValue(m_settings->serverCredentials(index.row())); + case CredentialsLoginRole: return m_settings->serverCredentials(index.row()).userName; + case IsDefaultRole: return index.row() == m_defaultServerIndex; + case IsCurrentlyProcessedRole: return index.row() == m_currenlyProcessedServerIndex; } return QVariant(); @@ -92,6 +86,7 @@ const int ServersModel::getServersCount() void ServersModel::setCurrentlyProcessedServerIndex(int index) { m_currenlyProcessedServerIndex = index; + emit currentlyProcessedServerIndexChanged(); } int ServersModel::getCurrentlyProcessedServerIndex() @@ -104,6 +99,12 @@ bool ServersModel::isDefaultServerCurrentlyProcessed() return m_defaultServerIndex == m_currenlyProcessedServerIndex; } +bool ServersModel::isCurrentlyProcessedServerHasWriteAccess() +{ + auto credentials = m_settings->serverCredentials(m_currenlyProcessedServerIndex); + return (!credentials.userName.isEmpty() && !credentials.secretData.isEmpty()); +} + void ServersModel::addServer(const QJsonObject &server) { beginResetModel(); @@ -119,18 +120,19 @@ void ServersModel::removeServer() m_servers = m_settings->serversArray(); if (m_settings->defaultServerIndex() == m_currenlyProcessedServerIndex) { - m_settings->setDefaultServer(0); + setDefaultServerIndex(0); } else if (m_settings->defaultServerIndex() > m_currenlyProcessedServerIndex) { - m_settings->setDefaultServer(m_settings->defaultServerIndex() - 1); + setDefaultServerIndex(m_settings->defaultServerIndex() - 1); } if (m_settings->serversCount() == 0) { - m_settings->setDefaultServer(-1); + setDefaultServerIndex(-1); } endResetModel(); } -QHash ServersModel::roleNames() const { +QHash ServersModel::roleNames() const +{ QHash roles; roles[NameRole] = "name"; roles[HostNameRole] = "hostName"; @@ -140,3 +142,9 @@ QHash ServersModel::roleNames() const { roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed"; return roles; } + +void ServersModel::setDefaultServerIndex(const int index) +{ + m_settings->setDefaultServer(index); + m_defaultServerIndex = m_settings->defaultServerIndex(); +} diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index be13d61b..891d6ad1 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -5,7 +5,8 @@ #include "settings.h" -struct ServerModelContent { +struct ServerModelContent +{ QString desc; QString address; bool isDefault; @@ -15,7 +16,7 @@ class ServersModel : public QAbstractListModel { Q_OBJECT public: - enum ServersModelRoles { + enum Roles { NameRole = Qt::UserRole + 1, HostNameRole, CredentialsRole, @@ -35,6 +36,7 @@ public: public slots: const int getDefaultServerIndex(); bool isDefaultServerCurrentlyProcessed(); + bool isCurrentlyProcessedServerHasWriteAccess(); const int getServersCount(); @@ -47,7 +49,12 @@ public slots: protected: QHash roleNames() const override; +signals: + void currentlyProcessedServerIndexChanged(); + private: + void setDefaultServerIndex(const int index); + QJsonArray m_servers; std::shared_ptr m_settings; diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml index 3c34a6b0..626c4ffb 100644 --- a/client/ui/qml/Components/ConnectButton.qml +++ b/client/ui/qml/Components/ConnectButton.qml @@ -15,7 +15,7 @@ Button { } } - text: qsTr("Connect") + text: ConnectionController.connectionStateText background: Item { clip: true @@ -26,13 +26,21 @@ Button { Image { id: border - source: connectionProccess.running ? "/images/connectionProgress.svg" : - ConnectionController.isConnected ? "/images/connectionOff.svg" : "/images/connectionOn.svg" + source: { + if (ConnectionController.isConnectionInProgress) { + return "/images/connectionProgress.svg" + } else if (ConnectionController.isConnected) { + return "/images/connectionOff.svg" + } else { + return "/images/connectionOn.svg" + } + } + RotationAnimator { id: connectionProccess target: border - running: false + running: ConnectionController.isConnectionInProgress from: 0 to: 360 loops: Animation.Infinite @@ -67,63 +75,12 @@ Button { } onClicked: { - connectionProccess.running ? ConnectionController.closeConnection() : ConnectionController.openConnection() - } - - Connections { - target: ConnectionController - function onConnectionStateChanged(state) { - switch(state) { - case ConnectionState.Unknown: { - console.log("Unknown") - break - } - case ConnectionState.Disconnected: { - console.log("Disconnected") - connectionProccess.running = false - root.text = qsTr("Connect") - ConnectionController.isConnected = false - break - } - case ConnectionState.Preparing: { - console.log("Preparing") - connectionProccess.running = true - root.text = qsTr("Connection...") - break - } - case ConnectionState.Connecting: { - console.log("Connecting") - connectionProccess.running = true - root.text = qsTr("Connection...") - break - } - case ConnectionState.Connected: { - console.log("Connected") - connectionProccess.running = false - root.text = qsTr("Disconnect") - ConnectionController.isConnected = true - break - } - case ConnectionState.Disconnecting: { - console.log("Disconnecting") - connectionProccess.running = true - root.text = qsTr("Disconnection...") - break - } - case ConnectionState.Reconnecting: { - console.log("Reconnecting") - connectionProccess.running = true - root.text = qsTr("Reconnection...") - break - } - case ConnectionState.Error: { - console.log("Error") - connectionProccess.running = false - root.text = qsTr("Connect") - PageController.showErrorMessage(ConnectionController.getLastConnectionError()) - break - } - } + if (ConnectionController.isConnectionInProgress) { + ConnectionController.closeConnection() + } else if (ConnectionController.isConnected) { + ConnectionController.closeConnection() + } else { + ConnectionController.openConnection() } } } diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 627eba81..e03738c8 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -141,6 +141,7 @@ DrawerType { Layout.bottomMargin: 16 padding: 0 + leftPadding: 0 height: 24 color: "#D7D8DB" diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml index b41f0b40..8ad92d54 100644 --- a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -2,6 +2,8 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import "TextTypes" + Item { id: root @@ -34,15 +36,10 @@ Item { anchors.fill: backgroud ColumnLayout { - Text { + LabelTextType { text: root.headerText color: "#878b91" - font.pixelSize: 13 - font.weight: 400 - font.family: "PT Root UI VF" - font.letterSpacing: 0.02 - height: 16 Layout.fillWidth: true Layout.rightMargin: 16 Layout.leftMargin: 16 diff --git a/client/ui/qml/Controls2/TextTypes/SmallTextType.qml b/client/ui/qml/Controls2/TextTypes/SmallTextType.qml new file mode 100644 index 00000000..96f3342d --- /dev/null +++ b/client/ui/qml/Controls2/TextTypes/SmallTextType.qml @@ -0,0 +1,12 @@ +import QtQuick + +Text { + height: 20 + + color: "#D7D8DB" + font.pixelSize: 14 + font.weight: Font.Normal + font.family: "PT Root UI VF" + + wrapMode: Text.WordWrap +} diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 282c1408..a4e90117 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -21,8 +21,8 @@ PageType { property string borderColor: "#2C2D30" - property string currentServerName: serversMenuContent.currentItem.delegateData.name - property string currentServerHostName: serversMenuContent.currentItem.delegateData.hostName + property string currentServerName + property string currentServerHostName property string currentContainerName ConnectButton { @@ -93,7 +93,15 @@ PageType { Layout.bottomMargin: 44 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - text: root.currentContainerName + " | " + root.currentServerHostName + text: { + var string = "" + if (SettingsController.isAmneziaDnsEnabled()) { + string += "Amnezia DNS | " + } + + string += root.currentContainerName + " | " + root.currentServerHostName + return string + } } } @@ -153,6 +161,8 @@ PageType { headerBackButtonImage: "qrc:/images/controls/arrow-left.svg" rootButtonClickedFunction: function() { + // todo check if server index changed before set Currently processed + // todo make signal slot for change server index in containersModel ServersModel.setCurrentlyProcessedServerIndex(serversMenuContent.currentIndex) ContainersModel.setCurrentlyProcessedServerIndex(serversMenuContent.currentIndex) containersDropDown.menuVisible = true @@ -161,20 +171,45 @@ PageType { listView: HomeContainersListView { rootWidth: root.width + Connections { + target: ServersModel + + function onCurrentlyProcessedServerIndexChanged() { + updateContainersModelFilters() + } + + function updateContainersModelFilters() { + if (ServersModel.isCurrentlyProcessedServerHasWriteAccess()) { + proxyContainersModel.filters = [serviceTypeFilter, supportedFilter] + } else { + proxyContainersModel.filters = installedFilter + } + } + } + + ValueFilter { + id: serviceTypeFilter + roleName: "serviceType" + value: ProtocolEnum.Vpn + } + ValueFilter { + id: supportedFilter + roleName: "isSupported" + value: true + } + ValueFilter { + id: installedFilter + roleName: "isInstalled" + value: true + } + model: SortFilterProxyModel { id: proxyContainersModel sourceModel: ContainersModel - filters: [ - ValueFilter { - roleName: "serviceType" - value: ProtocolEnum.Vpn - }, - ValueFilter { - roleName: "isSupported" - value: true - } - ] + + Component.onCompleted: updateContainersModelFilters() } + currentIndex: ContainersModel.getDefaultContainer() } } @@ -272,6 +307,9 @@ PageType { isDefault = true ContainersModel.setCurrentlyProcessedServerIndex(index) + + root.currentServerName = name + root.currentServerHostName = hostName } MouseArea { @@ -302,6 +340,13 @@ PageType { Layout.fillWidth: true } } + + Component.onCompleted: { + if (serversMenuContent.currentIndex === index) { + root.currentServerName = name + root.currentServerHostName = hostName + } + } } } } diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 6179983b..b98d2b8c 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -14,6 +14,21 @@ import "../Components" PageType { id: root + Connections { + target: InstallController + + function onScanServerFinished(isInstalledContainerFound) { + var message = "" + if (isInstalledContainerFound) { + message = qsTr("All installed containers have been added to the application") + } else { + message = qsTr("Не найдено установленных контейнеров") + } + + PageController.showErrorMessage(message) + } + } + FlickableType { id: fl anchors.top: parent.top @@ -30,8 +45,8 @@ PageType { LabelWithButtonType { Layout.fillWidth: true - text: "Clear Amnezia cache" - descriptionText: "May be needed when changing other settings" + text: qsTr("Clear Amnezia cache") + descriptionText: qsTr("May be needed when changing other settings") clickedFunction: function() { questionDrawer.headerText = qsTr("Clear cached profiles?") @@ -52,6 +67,19 @@ PageType { DividerType {} + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Проверить сервер на наличие ранее установленных сервисов Amnezia") + descriptionText: qsTr("Добавим их в приложение, если они не отображались") + + clickedFunction: function() { + InstallController.scanServerForInstalledContainers() + } + } + + DividerType {} + LabelWithButtonType { Layout.fillWidth: true diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index 3f037035..fa6b1f92 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -7,6 +7,7 @@ import PageEnum 1.0 import "./" import "../Controls2" import "../Config" +import "../Controls2/TextTypes" PageType { id: root @@ -42,28 +43,32 @@ PageType { HeaderType { Layout.fillWidth: true - headerText: "Подключение к серверу" + headerText: qsTr("Server connection") } TextFieldWithHeaderType { id: hostname Layout.fillWidth: true - headerText: "Server IP address [:port]" + headerText: qsTr("Server IP address [:port]") + textFieldPlaceholderText: qsTr("Enter the address in the format 255.255.255.255:88") + textField.validator: RegularExpressionValidator { + regularExpression: InstallController.ipAddressPortRegExp() + } } TextFieldWithHeaderType { id: username Layout.fillWidth: true - headerText: "Login to connect via SSH" + headerText: qsTr("Login to connect via SSH") } TextFieldWithHeaderType { id: secretData Layout.fillWidth: true - headerText: "Password / Private key" + headerText: qsTr("Password / Private key") textField.echoMode: TextInput.Password } @@ -71,7 +76,7 @@ PageType { Layout.fillWidth: true Layout.topMargin: 24 - text: qsTr("Настроить сервер простым образом") + text: qsTr("Set up a server the easy way") onClicked: function() { InstallController.setShouldCreateServer(true) @@ -92,7 +97,7 @@ PageType { textColor: "#D7D8DB" borderWidth: 1 - text: qsTr("Выбрать протокол для установки") + text: qsTr("Select protocol to install") onClicked: function() { InstallController.setShouldCreateServer(true) diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index 9631e258..d0fdeabd 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -24,7 +24,7 @@ PageType { PageController.showErrorMessage(errorMessage) } - function onInstallContainerFinished() { + function onInstallContainerFinished(isInstalledContainerFound) { goToStartPage() if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) { PageController.restorePageHomeState(true) @@ -34,9 +34,15 @@ PageType { } else { goToPage(PageEnum.PageHome) } + + if (isInstalledContainerFound) { + //todo change to info message + PageController.showErrorMessage(qsTr("The container you are trying to install is already installed on the server. " + + "All installed containers have been added to the application")) + } } - function onInstallServerFinished() { + function onInstallServerFinished(isInstalledContainerFound) { goToStartPage() if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) { PageController.restorePageHomeState() @@ -46,6 +52,19 @@ PageType { var pagePath = PageController.getPagePath(PageEnum.PageStart) stackView.replace(pagePath, { "objectName" : pagePath }) } + + if (isInstalledContainerFound) { + PageController.showErrorMessage(qsTr("The container you are trying to install is already installed on the server. " + + "All installed containers have been added to the application")) + } + } + + function onServerAlreadyExists(serverIndex) { + goToStartPage() + ServersModel.setCurrentlyProcessedServerIndex(serverIndex) + goToPage(PageEnum.PageSettingsServerInfo, false) + + PageController.showErrorMessage(qsTr("The server has already been added to the application")) } } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index a91cab6a..1307ee05 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -20,6 +20,11 @@ PageType { tabBarStackView.goToTabBarPage(PageController.getPagePath(PageEnum.PageHome)) } + function onGoToPageSettings() { + tabBar.currentIndex = 2 + tabBarStackView.goToTabBarPage(PageController.getPagePath(PageEnum.PageSettings)) + } + function onShowErrorMessage(errorMessage) { popupErrorMessage.popupErrorMessageText = errorMessage popupErrorMessage.open() From 2ef53c6df9b7d520058dd075c2fb013292ada48a Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 23 Jun 2023 15:24:40 +0900 Subject: [PATCH 035/278] added separation for read/write and readonly servers for pageSettingsServerProtocols, PageSettingsServerServices, PageSettingsServerData - added fields validations for pageSetupWizardCredentials --- client/amnezia_application.cpp | 94 +++++------ client/resources.qrc | 1 + client/ui/controllers/exportController.cpp | 55 +++---- client/ui/controllers/importController.cpp | 70 ++++---- client/ui/controllers/installController.cpp | 3 +- client/ui/models/containers_model.cpp | 2 +- client/ui/models/containers_model.h | 7 +- client/ui/models/servers_model.cpp | 30 ++-- client/ui/models/servers_model.h | 23 ++- client/ui/pages_logic/ServerListLogic.cpp | 38 +++-- .../qml/Controls2/TextFieldWithHeaderType.qml | 151 ++++++++++-------- .../ui/qml/Filters/ContainersModelFilters.qml | 47 ++++++ client/ui/qml/Pages2/PageHome.qml | 46 ++---- .../ui/qml/Pages2/PageSettingsServerData.qml | 29 +++- .../ui/qml/Pages2/PageSettingsServerInfo.qml | 14 +- .../Pages2/PageSettingsServerProtocols.qml | 40 +++-- .../qml/Pages2/PageSettingsServerServices.qml | 40 +++-- .../ui/qml/Pages2/PageSettingsServersList.qml | 3 +- .../qml/Pages2/PageSetupWizardCredentials.qml | 29 ++++ .../qml/Pages2/PageSetupWizardInstalling.qml | 2 +- client/ui/qml/Pages2/PageShare.qml | 40 +++-- client/ui/qml/Pages2/PageStart.qml | 27 +++- 22 files changed, 466 insertions(+), 325 deletions(-) create mode 100644 client/ui/qml/Filters/ContainersModelFilters.qml diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index b9aa0f74..b89e5ba9 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -1,29 +1,28 @@ #include "amnezia_application.h" #include +#include #include #include #include -#include -#include "logger.h" #include "defines.h" +#include "logger.h" #include "platforms/ios/QRCodeReaderBase.h" #include "ui/pages.h" #if defined(Q_OS_IOS) -#include "platforms/ios/QtAppDelegate-C-Interface.h" + #include "platforms/ios/QtAppDelegate-C-Interface.h" #endif #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) - AmneziaApplication::AmneziaApplication(int &argc, char *argv[]): - AMNEZIA_BASE_CLASS(argc, argv) +AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv) #else - AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecondary, - SingleApplication::Options options, int timeout, const QString &userData): - SingleApplication(argc, argv, allowSecondary, options, timeout, userData) +AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecondary, SingleApplication::Options options, + int timeout, const QString &userData) + : SingleApplication(argc, argv, allowSecondary, options, timeout, userData) #endif { setQuitOnLastWindowClosed(false); @@ -51,12 +50,14 @@ AmneziaApplication::~AmneziaApplication() { if (m_engine) { - QObject::disconnect(m_engine, 0,0,0); + QObject::disconnect(m_engine, 0, 0, 0); delete m_engine; } - if (m_protocolProps) delete m_protocolProps; - if (m_containerProps) delete m_containerProps; + if (m_protocolProps) + delete m_protocolProps; + if (m_containerProps) + delete m_containerProps; } void AmneziaApplication::init() @@ -64,11 +65,13 @@ void AmneziaApplication::init() m_engine = new QQmlApplicationEngine; const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml")); - QObject::connect(m_engine, &QQmlApplicationEngine::objectCreated, - this, [url](QObject *obj, const QUrl &objUrl) { - if (!obj && url == objUrl) - QCoreApplication::exit(-1); - }, Qt::QueuedConnection); + QObject::connect( + m_engine, &QQmlApplicationEngine::objectCreated, this, + [url](QObject *obj, const QUrl &objUrl) { + if (!obj && url == objUrl) + QCoreApplication::exit(-1); + }, + Qt::QueuedConnection); m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance()); @@ -78,6 +81,8 @@ void AmneziaApplication::init() m_serversModel.reset(new ServersModel(m_settings, this)); m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get()); + connect(m_serversModel.get(), &ServersModel::currentlyProcessedServerIndexChanged, m_containersModel.get(), + &ContainersModel::setCurrentlyProcessedServerIndex); m_configurator = std::shared_ptr(new VpnConfigurator(m_settings, this)); m_vpnConnection.reset(new VpnConnection(m_settings, m_configurator)); @@ -94,21 +99,19 @@ void AmneziaApplication::init() m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings)); m_engine->rootContext()->setContextProperty("ImportController", m_importController.get()); - m_exportController.reset( - new ExportController(m_serversModel, m_containersModel, m_settings, m_configurator)); + m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_settings, m_configurator)); m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get()); - m_settingsController.reset( - new SettingsController(m_serversModel, m_containersModel, m_settings)); + m_settingsController.reset(new SettingsController(m_serversModel, m_containersModel, m_settings)); m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get()); // m_engine->load(url); -// if (m_engine->rootObjects().size() > 0) { -// m_uiLogic->setQmlRoot(m_engine->rootObjects().at(0)); -// } + // if (m_engine->rootObjects().size() > 0) { + // m_uiLogic->setQmlRoot(m_engine->rootObjects().at(0)); + // } if (m_settings->isSaveLogs()) { if (!Logger::init()) { @@ -116,24 +119,23 @@ void AmneziaApplication::init() } } -//#ifdef Q_OS_WIN -// if (m_parser.isSet("a")) m_uiLogic->showOnStartup(); -// else emit m_uiLogic->show(); -//#else -// m_uiLogic->showOnStartup(); -//#endif - -// // TODO - fix -//#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) -// if (isPrimary()) { -// QObject::connect(this, &SingleApplication::instanceStarted, m_uiLogic, [this](){ -// qDebug() << "Secondary instance started, showing this window instead"; -// emit m_uiLogic->show(); -// emit m_uiLogic->raise(); -// }); -// } -//#endif + // #ifdef Q_OS_WIN + // if (m_parser.isSet("a")) m_uiLogic->showOnStartup(); + // else emit m_uiLogic->show(); + // #else + // m_uiLogic->showOnStartup(); + // #endif + // // TODO - fix + // #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) + // if (isPrimary()) { + // QObject::connect(this, &SingleApplication::instanceStarted, m_uiLogic, [this](){ + // qDebug() << "Secondary instance started, showing this window instead"; + // emit m_uiLogic->show(); + // emit m_uiLogic->raise(); + // }); + // } + // #endif } void AmneziaApplication::registerTypes() @@ -156,6 +158,9 @@ void AmneziaApplication::registerTypes() m_protocolProps = new ProtocolProps; qmlRegisterSingletonInstance("ProtocolProps", 1, 0, "ProtocolProps", m_protocolProps); + qmlRegisterSingletonType(QUrl("qrc:/ui/qml/Filters/ContainersModelFilters.qml"), "ContainersModelFilters", 1, 0, + "ContainersModelFilters"); + // Vpn::declareQmlVpnConnectionStateEnum(); PageLoader::declareQmlPageEnum(); @@ -182,19 +187,17 @@ bool AmneziaApplication::parseCommands() m_parser.addHelpOption(); m_parser.addVersionOption(); - QCommandLineOption c_autostart {{"a", "autostart"}, "System autostart"}; + QCommandLineOption c_autostart { { "a", "autostart" }, "System autostart" }; m_parser.addOption(c_autostart); - QCommandLineOption c_cleanup {{"c", "cleanup"}, "Cleanup logs"}; + QCommandLineOption c_cleanup { { "c", "cleanup" }, "Cleanup logs" }; m_parser.addOption(c_cleanup); m_parser.process(*this); if (m_parser.isSet(c_cleanup)) { Logger::cleanUp(); - QTimer::singleShot(100, this, [this]{ - quit(); - }); + QTimer::singleShot(100, this, [this] { quit(); }); exec(); return false; } @@ -205,4 +208,3 @@ QQmlApplicationEngine *AmneziaApplication::qmlEngine() const { return m_engine; } - diff --git a/client/resources.qrc b/client/resources.qrc index 5b5fd593..9aba0a6b 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -269,5 +269,6 @@ images/controls/mail.svg images/controls/telegram.svg ui/qml/Controls2/TextTypes/SmallTextType.qml + ui/qml/Filters/ContainersModelFilters.qml diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index b6cd7abb..04264624 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -16,14 +16,14 @@ ExportController::ExportController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, const std::shared_ptr &settings, - const std::shared_ptr &configurator, - QObject *parent) - : QObject(parent) - , m_serversModel(serversModel) - , m_containersModel(containersModel) - , m_settings(settings) - , m_configurator(configurator) -{} + const std::shared_ptr &configurator, QObject *parent) + : QObject(parent), + m_serversModel(serversModel), + m_containersModel(containersModel), + m_settings(settings), + m_configurator(configurator) +{ +} void ExportController::generateFullAccessConfig() { @@ -33,8 +33,8 @@ void ExportController::generateFullAccessConfig() QByteArray compressedConfig = QJsonDocument(config).toJson(); compressedConfig = qCompress(compressedConfig, 8); m_amneziaCode = QString("vpn://%1") - .arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding - | QByteArray::OmitTrailingEquals))); + .arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding + | QByteArray::OmitTrailingEquals))); m_qrCodes = generateQrCodeImageSeries(compressedConfig); emit exportConfigChanged(); @@ -43,25 +43,21 @@ void ExportController::generateFullAccessConfig() void ExportController::generateConnectionConfig() { int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); - ServerCredentials credentials = qvariant_cast( - m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); + ServerCredentials credentials = + qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); - DockerContainer container = static_cast( - m_containersModel->getCurrentlyProcessedContainerIndex()); + DockerContainer container = static_cast(m_containersModel->getCurrentlyProcessedContainerIndex()); QModelIndex containerModelIndex = m_containersModel->index(container); - QJsonObject containerConfig = qvariant_cast( - m_containersModel->data(containerModelIndex, ContainersModel::Roles::ConfigRole)); + QJsonObject containerConfig = + qvariant_cast(m_containersModel->data(containerModelIndex, ContainersModel::Roles::ConfigRole)); containerConfig.insert(config_key::container, ContainerProps::containerToString(container)); ErrorCode errorCode = ErrorCode::NoError; for (Proto protocol : ContainerProps::protocolsForContainer(container)) { QJsonObject protocolConfig = m_settings->protocolConfig(serverIndex, container, protocol); - QString vpnConfig = m_configurator->genVpnProtocolConfig(credentials, - container, - containerConfig, - protocol, - &errorCode); + QString vpnConfig = + m_configurator->genVpnProtocolConfig(credentials, container, containerConfig, protocol, &errorCode); if (errorCode) { emit exportErrorOccurred(errorString(errorCode)); return; @@ -75,7 +71,7 @@ void ExportController::generateConnectionConfig() config.remove(config_key::userName); config.remove(config_key::password); config.remove(config_key::port); - config.insert(config_key::containers, QJsonArray{containerConfig}); + config.insert(config_key::containers, QJsonArray { containerConfig }); config.insert(config_key::defaultContainer, ContainerProps::containerToString(container)); auto dns = m_configurator->getDnsForConfig(serverIndex); @@ -86,8 +82,8 @@ void ExportController::generateConnectionConfig() QByteArray compressedConfig = QJsonDocument(config).toJson(); compressedConfig = qCompress(compressedConfig, 8); m_amneziaCode = QString("vpn://%1") - .arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding - | QByteArray::OmitTrailingEquals))); + .arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding + | QByteArray::OmitTrailingEquals))); m_qrCodes = generateQrCodeImageSeries(compressedConfig); emit exportConfigChanged(); @@ -108,10 +104,8 @@ void ExportController::saveFile() QString fileExtension = ".vpn"; QString docDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); QUrl fileName; - fileName = QFileDialog::getSaveFileUrl(nullptr, - tr("Save AmneziaVPN config"), - QUrl::fromLocalFile(docDir + "/" + "amnezia_config"), - "*" + fileExtension); + fileName = QFileDialog::getSaveFileUrl(nullptr, tr("Save AmneziaVPN config"), + QUrl::fromLocalFile(docDir + "/" + "amnezia_config"), "*" + fileExtension); if (fileName.isEmpty()) return; if (!fileName.toString().endsWith(fileExtension)) { @@ -139,10 +133,9 @@ QList ExportController::generateQrCodeImageSeries(const QByteArray &dat for (int i = 0; i < data.size(); i = i + k) { QByteArray chunk; QDataStream s(&chunk, QIODevice::WriteOnly); - s << amnezia::qrMagicCode << chunksCount << (quint8) std::round(i / k) << data.mid(i, k); + s << amnezia::qrMagicCode << chunksCount << (quint8)std::round(i / k) << data.mid(i, k); - QByteArray ba = chunk.toBase64(QByteArray::Base64UrlEncoding - | QByteArray::OmitTrailingEquals); + QByteArray ba = chunk.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(ba, qrcodegen::QrCode::Ecc::LOW); QString svg = QString::fromStdString(toSvgString(qr, 0)); diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 99de3131..5b4b7a83 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -5,40 +5,42 @@ #include "core/errorstrings.h" -namespace { -enum class ConfigTypes { Amnezia, OpenVpn, WireGuard }; - -ConfigTypes checkConfigFormat(const QString &config) +namespace { - const QString openVpnConfigPatternCli = "client"; - const QString openVpnConfigPatternProto1 = "proto tcp"; - const QString openVpnConfigPatternProto2 = "proto udp"; - const QString openVpnConfigPatternDriver1 = "dev tun"; - const QString openVpnConfigPatternDriver2 = "dev tap"; + enum class ConfigTypes { + Amnezia, + OpenVpn, + WireGuard + }; - const QString wireguardConfigPatternSectionInterface = "[Interface]"; - const QString wireguardConfigPatternSectionPeer = "[Peer]"; + ConfigTypes checkConfigFormat(const QString &config) + { + const QString openVpnConfigPatternCli = "client"; + const QString openVpnConfigPatternProto1 = "proto tcp"; + const QString openVpnConfigPatternProto2 = "proto udp"; + const QString openVpnConfigPatternDriver1 = "dev tun"; + const QString openVpnConfigPatternDriver2 = "dev tap"; - if (config.contains(openVpnConfigPatternCli) - && (config.contains(openVpnConfigPatternProto1) - || config.contains(openVpnConfigPatternProto2)) - && (config.contains(openVpnConfigPatternDriver1) - || config.contains(openVpnConfigPatternDriver2))) { - return ConfigTypes::OpenVpn; - } else if (config.contains(wireguardConfigPatternSectionInterface) - && config.contains(wireguardConfigPatternSectionPeer)) { - return ConfigTypes::WireGuard; + const QString wireguardConfigPatternSectionInterface = "[Interface]"; + const QString wireguardConfigPatternSectionPeer = "[Peer]"; + + if (config.contains(openVpnConfigPatternCli) + && (config.contains(openVpnConfigPatternProto1) || config.contains(openVpnConfigPatternProto2)) + && (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) { + return ConfigTypes::OpenVpn; + } else if (config.contains(wireguardConfigPatternSectionInterface) + && config.contains(wireguardConfigPatternSectionPeer)) { + return ConfigTypes::WireGuard; + } + return ConfigTypes::Amnezia; } - return ConfigTypes::Amnezia; -} } // namespace ImportController::ImportController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, - const std::shared_ptr &settings, - QObject *parent) : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings) + const std::shared_ptr &settings, QObject *parent) + : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings) { - } void ImportController::extractConfigFromFile(const QUrl &fileUrl) @@ -88,8 +90,7 @@ void ImportController::importConfig() m_serversModel->addServer(m_config); if (!m_config.value(config_key::containers).toArray().isEmpty()) { - auto newServerIndex = m_serversModel->index(m_serversModel->getServersCount() - 1); - m_serversModel->setData(newServerIndex, true, ServersModel::Roles::IsDefaultRole); + m_serversModel->setDefaultServerIndex(m_serversModel->getServersCount() - 1); } emit importFinished(); @@ -116,12 +117,12 @@ QJsonObject ImportController::extractAmneziaConfig(QString &data) return QJsonDocument::fromJson(ba).object(); } -//bool ImportController::importConnectionFromQr(const QByteArray &data) +// bool ImportController::importConnectionFromQr(const QByteArray &data) //{ -// QJsonObject dataObj = QJsonDocument::fromJson(data).object(); -// if (!dataObj.isEmpty()) { -// return importConnection(dataObj); -// } +// QJsonObject dataObj = QJsonDocument::fromJson(data).object(); +// if (!dataObj.isEmpty()) { +// return importConnection(dataObj); +// } // QByteArray ba_uncompressed = qUncompress(data); // if (!ba_uncompressed.isEmpty()) { @@ -159,7 +160,6 @@ QJsonObject ImportController::extractOpenVpnConfig(const QString &data) config[config_key::defaultContainer] = "amnezia-openvpn"; config[config_key::description] = m_settings->nextAvailableServerName(); - const static QRegularExpression dnsRegExp("dhcp-option DNS (\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)"); QRegularExpressionMatchIterator dnsMatch = dnsRegExp.globalMatch(data); if (dnsMatch.hasNext()) { @@ -206,7 +206,9 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data) config[config_key::defaultContainer] = "amnezia-wireguard"; config[config_key::description] = m_settings->nextAvailableServerName(); - const static QRegularExpression dnsRegExp("DNS = (\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b).*(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)"); + const static QRegularExpression dnsRegExp( + "DNS = " + "(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b).*(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)"); QRegularExpressionMatch dnsMatch = dnsRegExp.match(data); if (dnsMatch.hasMatch()) { config[config_key::dns1] = dnsMatch.captured(1); diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 5d3bfc2f..1809e082 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -67,8 +67,7 @@ void InstallController::installServer(DockerContainer container, QJsonObject &co server.insert(config_key::defaultContainer, ContainerProps::containerToString(container)); m_serversModel->addServer(server); - auto newServerIndex = m_serversModel->index(m_serversModel->getServersCount() - 1); - m_serversModel->setData(newServerIndex, true, ServersModel::Roles::IsDefaultRole); + m_serversModel->setDefaultServerIndex(m_serversModel->getServersCount() - 1); emit installServerFinished(isInstalledContainerFound); return; diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index f095dd02..7a6946f5 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -78,7 +78,7 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const return QVariant(); } -void ContainersModel::setCurrentlyProcessedServerIndex(int index) +void ContainersModel::setCurrentlyProcessedServerIndex(const int index) { beginResetModel(); m_currentlyProcessedServerIndex = index; diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index 7bb58755..ebf47497 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -3,11 +3,11 @@ #include #include -#include #include +#include -#include "settings.h" #include "containers/containers_defs.h" +#include "settings.h" class ContainersModel : public QAbstractListModel { @@ -44,7 +44,7 @@ public slots: DockerContainer getDefaultContainer(); QString getDefaultContainerName(); - void setCurrentlyProcessedServerIndex(int index); + void setCurrentlyProcessedServerIndex(const int index); void setCurrentlyProcessedContainerIndex(int index); int getCurrentlyProcessedContainerIndex(); @@ -57,7 +57,6 @@ protected: private: QMap m_containers; - int m_currentlyProcessedServerIndex; int m_currentlyProcessedContainerIndex; DockerContainer m_defaultContainerIndex; diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 94267ed1..9df243fe 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -5,6 +5,7 @@ ServersModel::ServersModel(std::shared_ptr settings, QObject *parent) { m_servers = m_settings->serversArray(); m_defaultServerIndex = m_settings->defaultServerIndex(); + m_currenlyProcessedServerIndex = m_defaultServerIndex; } int ServersModel::rowCount(const QModelIndex &parent) const @@ -28,10 +29,6 @@ bool ServersModel::setData(const QModelIndex &index, const QVariant &value, int m_servers.replace(index.row(), server); break; } - case IsDefaultRole: { - setDefaultServerIndex(index.row()); - break; - } default: { return true; } @@ -62,6 +59,10 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const case CredentialsLoginRole: return m_settings->serverCredentials(index.row()).userName; case IsDefaultRole: return index.row() == m_defaultServerIndex; case IsCurrentlyProcessedRole: return index.row() == m_currenlyProcessedServerIndex; + case HasWriteAccess: { + auto credentials = m_settings->serverCredentials(index.row()); + return (!credentials.userName.isEmpty() && !credentials.secretData.isEmpty()); + } } return QVariant(); @@ -73,6 +74,13 @@ QVariant ServersModel::data(const int index, int role) const return data(modelIndex, role); } +void ServersModel::setDefaultServerIndex(const int index) +{ + m_settings->setDefaultServer(index); + m_defaultServerIndex = m_settings->defaultServerIndex(); + emit defaultServerIndexChanged(); +} + const int ServersModel::getDefaultServerIndex() { return m_defaultServerIndex; @@ -83,10 +91,10 @@ const int ServersModel::getServersCount() return m_servers.count(); } -void ServersModel::setCurrentlyProcessedServerIndex(int index) +void ServersModel::setCurrentlyProcessedServerIndex(const int index) { m_currenlyProcessedServerIndex = index; - emit currentlyProcessedServerIndexChanged(); + emit currentlyProcessedServerIndexChanged(m_currenlyProcessedServerIndex); } int ServersModel::getCurrentlyProcessedServerIndex() @@ -101,8 +109,7 @@ bool ServersModel::isDefaultServerCurrentlyProcessed() bool ServersModel::isCurrentlyProcessedServerHasWriteAccess() { - auto credentials = m_settings->serverCredentials(m_currenlyProcessedServerIndex); - return (!credentials.userName.isEmpty() && !credentials.secretData.isEmpty()); + return qvariant_cast(data(m_currenlyProcessedServerIndex, HasWriteAccess)); } void ServersModel::addServer(const QJsonObject &server) @@ -140,11 +147,6 @@ QHash ServersModel::roleNames() const roles[CredentialsLoginRole] = "credentialsLogin"; roles[IsDefaultRole] = "isDefault"; roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed"; + roles[HasWriteAccess] = "hasWriteAccess"; return roles; } - -void ServersModel::setDefaultServerIndex(const int index) -{ - m_settings->setDefaultServer(index); - m_defaultServerIndex = m_settings->defaultServerIndex(); -} diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index 891d6ad1..6cb9859e 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -5,13 +5,6 @@ #include "settings.h" -struct ServerModelContent -{ - QString desc; - QString address; - bool isDefault; -}; - class ServersModel : public QAbstractListModel { Q_OBJECT @@ -22,7 +15,8 @@ public: CredentialsRole, CredentialsLoginRole, IsDefaultRole, - IsCurrentlyProcessedRole + IsCurrentlyProcessedRole, + HasWriteAccess }; ServersModel(std::shared_ptr settings, QObject *parent = nullptr); @@ -33,14 +27,20 @@ public: QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant data(const int index, int role = Qt::DisplayRole) const; + Q_PROPERTY(int defaultIndex READ getDefaultServerIndex WRITE setDefaultServerIndex NOTIFY defaultServerIndexChanged) + Q_PROPERTY(int currentlyProcessedIndex READ getCurrentlyProcessedServerIndex WRITE setCurrentlyProcessedServerIndex + NOTIFY currentlyProcessedServerIndexChanged) + public slots: + void setDefaultServerIndex(const int index); const int getDefaultServerIndex(); bool isDefaultServerCurrentlyProcessed(); + bool isCurrentlyProcessedServerHasWriteAccess(); const int getServersCount(); - void setCurrentlyProcessedServerIndex(int index); + void setCurrentlyProcessedServerIndex(const int index); int getCurrentlyProcessedServerIndex(); void addServer(const QJsonObject &server); @@ -50,11 +50,10 @@ protected: QHash roleNames() const override; signals: - void currentlyProcessedServerIndexChanged(); + void currentlyProcessedServerIndexChanged(const int index); + void defaultServerIndexChanged(); private: - void setDefaultServerIndex(const int index); - QJsonArray m_servers; std::shared_ptr m_settings; diff --git a/client/ui/pages_logic/ServerListLogic.cpp b/client/ui/pages_logic/ServerListLogic.cpp index 56a682b8..16775bc0 100644 --- a/client/ui/pages_logic/ServerListLogic.cpp +++ b/client/ui/pages_logic/ServerListLogic.cpp @@ -1,14 +1,12 @@ #include "ServerListLogic.h" -#include "vpnconnection.h" #include "../models/servers_model.h" #include "../uilogic.h" +#include "vpnconnection.h" -ServerListLogic::ServerListLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent), - m_serverListModel{new ServersModel(m_settings, this)} +ServerListLogic::ServerListLogic(UiLogic *logic, QObject *parent) + : PageLogicBase(logic, parent), m_serverListModel { new ServersModel(m_settings, this) } { - } void ServerListLogic::onServerListPushbuttonDefaultClicked(int index) @@ -31,19 +29,19 @@ int ServerListLogic::currServerIdx() const void ServerListLogic::onUpdatePage() { - const QJsonArray &servers = m_settings->serversArray(); - int defaultServer = m_settings->defaultServerIndex(); - QVector serverListContent; - for(int i = 0; i < servers.size(); i++) { - ServerModelContent c; - auto server = servers.at(i).toObject(); - c.desc = server.value(config_key::description).toString(); - c.address = server.value(config_key::hostName).toString(); - if (c.desc.isEmpty()) { - c.desc = c.address; - } - c.isDefault = (i == defaultServer); - serverListContent.push_back(c); - } -// qobject_cast(m_serverListModel)->setContent(serverListContent); + // const QJsonArray &servers = m_settings->serversArray(); + // int defaultServer = m_settings->defaultServerIndex(); + // QVector serverListContent; + // for(int i = 0; i < servers.size(); i++) { + // ServerModelContent c; + // auto server = servers.at(i).toObject(); + // c.desc = server.value(config_key::description).toString(); + // c.address = server.value(config_key::hostName).toString(); + // if (c.desc.isEmpty()) { + // c.desc = c.address; + // } + // c.isDefault = (i == defaultServer); + // serverListContent.push_back(c); + // } + // qobject_cast(m_serverListModel)->setContent(serverListContent); } diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml index 8ad92d54..85d651ef 100644 --- a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -8,96 +8,115 @@ Item { id: root property string headerText - property string textFieldPlaceholderText - property bool textFieldEditable: true + property alias errorText: errorField.text property string buttonText property var clickedFunc property alias textField: textField property alias textFieldText: textField.text + property string textFieldPlaceholderText + property bool textFieldEditable: true - implicitHeight: 74 + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight - Rectangle { - id: backgroud + ColumnLayout { + id: content anchors.fill: parent - color: "#1c1d21" - radius: 16 - border.color: textField.focus ? "#d7d8db" : "#2C2D30" - border.width: 1 - Behavior on border.color { - PropertyAnimation { duration: 200 } - } - } + Rectangle { + id: backgroud + Layout.fillWidth: true + Layout.preferredHeight: 74 + color: "#1c1d21" + radius: 16 + border.color: textField.focus ? "#d7d8db" : "#2C2D30" + border.width: 1 - RowLayout { - anchors.fill: backgroud - ColumnLayout { - - LabelTextType { - text: root.headerText - color: "#878b91" - - Layout.fillWidth: true - Layout.rightMargin: 16 - Layout.leftMargin: 16 - Layout.topMargin: 16 + Behavior on border.color { + PropertyAnimation { duration: 200 } } - TextField { - id: textField + RowLayout { + anchors.fill: backgroud + ColumnLayout { + LabelTextType { + text: root.headerText + color: "#878b91" - enabled: root.textFieldEditable - color: "#d7d8db" + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.topMargin: 16 + } - placeholderText: textFieldPlaceholderText - placeholderTextColor: "#494B50" + TextField { + id: textField - selectionColor: "#412102" - selectedTextColor: "#D7D8DB" + enabled: root.textFieldEditable + color: "#d7d8db" - font.pixelSize: 16 - font.weight: 400 - font.family: "PT Root UI VF" + placeholderText: textFieldPlaceholderText + placeholderTextColor: "#494B50" - height: 24 - Layout.fillWidth: true - Layout.rightMargin: 16 - Layout.leftMargin: 16 - Layout.bottomMargin: 16 - topPadding: 0 - rightPadding: 0 - leftPadding: 0 - bottomPadding: 0 + selectionColor: "#412102" + selectedTextColor: "#D7D8DB" - background: Rectangle { - anchors.fill: parent - color: "#1c1d21" + font.pixelSize: 16 + font.weight: 400 + font.family: "PT Root UI VF" + + height: 24 + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + Layout.bottomMargin: 16 + topPadding: 0 + rightPadding: 0 + leftPadding: 0 + bottomPadding: 0 + + background: Rectangle { + anchors.fill: parent + color: "#1c1d21" + } + + onTextChanged: { + root.errorText = "" + } + } + } + + BasicButtonType { + visible: root.buttonText !== "" + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#D7D8DB" + borderWidth: 0 + + text: buttonText + + Layout.rightMargin: 24 + + onClicked: { + if (clickedFunc && typeof clickedFunc === "function") { + clickedFunc() + } + } } } } - BasicButtonType { - visible: root.buttonText !== "" + SmallTextType { + id: errorField - defaultColor: "transparent" - hoveredColor: Qt.rgba(1, 1, 1, 0.08) - pressedColor: Qt.rgba(1, 1, 1, 0.12) - disabledColor: "#878B91" - textColor: "#D7D8DB" - borderWidth: 0 - - text: buttonText - - Layout.rightMargin: 24 - - onClicked: { - if (clickedFunc && typeof clickedFunc === "function") { - clickedFunc() - } - } + text: root.errorText + visible: root.errorText !== "" + color: "#EB5757" } } } diff --git a/client/ui/qml/Filters/ContainersModelFilters.qml b/client/ui/qml/Filters/ContainersModelFilters.qml new file mode 100644 index 00000000..fa8bd83b --- /dev/null +++ b/client/ui/qml/Filters/ContainersModelFilters.qml @@ -0,0 +1,47 @@ +pragma Singleton + +import QtQuick 2.15 + +import SortFilterProxyModel 0.2 + +import ProtocolEnum 1.0 + +Item { + ValueFilter { + id: vpnTypeFilter + roleName: "serviceType" + value: ProtocolEnum.Vpn + } + + ValueFilter { + id: serviceTypeFilter + roleName: "serviceType" + value: ProtocolEnum.Other + } + + ValueFilter { + id: supportedFilter + roleName: "isSupported" + value: true + } + + ValueFilter { + id: installedFilter + roleName: "isInstalled" + value: true + } + + function getWriteAccessProtocolsListFilters() { + return [vpnTypeFilter, supportedFilter] + } + function getReadAccessProtocolsListFilters() { + return [vpnTypeFilter, supportedFilter, installedFilter] + } + + function getWriteAccessServicesListFilters() { + return [serviceTypeFilter, supportedFilter] + } + function getReadAccessServicesListFilters() { + return [serviceTypeFilter, supportedFilter, installedFilter] + } +} diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index a4e90117..9f121273 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -7,6 +7,7 @@ import SortFilterProxyModel 0.2 import PageEnum 1.0 import ProtocolEnum 1.0 import ContainerProps 1.0 +import ContainersModelFilters 1.0 import "./" import "../Controls2" @@ -161,10 +162,7 @@ PageType { headerBackButtonImage: "qrc:/images/controls/arrow-left.svg" rootButtonClickedFunction: function() { - // todo check if server index changed before set Currently processed - // todo make signal slot for change server index in containersModel - ServersModel.setCurrentlyProcessedServerIndex(serversMenuContent.currentIndex) - ContainersModel.setCurrentlyProcessedServerIndex(serversMenuContent.currentIndex) + ServersModel.currentlyProcessedIndex = serversMenuContent.currentIndex containersDropDown.menuVisible = true } @@ -177,39 +175,22 @@ PageType { function onCurrentlyProcessedServerIndexChanged() { updateContainersModelFilters() } + } - function updateContainersModelFilters() { - if (ServersModel.isCurrentlyProcessedServerHasWriteAccess()) { - proxyContainersModel.filters = [serviceTypeFilter, supportedFilter] - } else { - proxyContainersModel.filters = installedFilter - } + function updateContainersModelFilters() { + if (ServersModel.isCurrentlyProcessedServerHasWriteAccess()) { + proxyContainersModel.filters = ContainersModelFilters.getWriteAccessProtocolsListFilters() + } else { + proxyContainersModel.filters = ContainersModelFilters.getReadAccessProtocolsListFilters() } } - ValueFilter { - id: serviceTypeFilter - roleName: "serviceType" - value: ProtocolEnum.Vpn - } - ValueFilter { - id: supportedFilter - roleName: "isSupported" - value: true - } - ValueFilter { - id: installedFilter - roleName: "isInstalled" - value: true - } - model: SortFilterProxyModel { id: proxyContainersModel sourceModel: ContainersModel - - Component.onCompleted: updateContainersModelFilters() } + Component.onCompleted: updateContainersModelFilters() currentIndex: ContainersModel.getDefaultContainer() } } @@ -267,7 +248,7 @@ PageType { height: serversMenuContent.contentItem.height model: ServersModel - currentIndex: ServersModel.getDefaultServerIndex() + currentIndex: ServersModel.defaultIndex clip: true interactive: false @@ -305,8 +286,8 @@ PageType { onClicked: { serversMenuContent.currentIndex = index - isDefault = true - ContainersModel.setCurrentlyProcessedServerIndex(index) + ServersModel.currentlyProcessedIndex = index + ServersModel.defaultIndex = index root.currentServerName = name root.currentServerHostName = hostName @@ -328,8 +309,7 @@ PageType { z: 1 onClicked: function() { - ServersModel.setCurrentlyProcessedServerIndex(index) - ContainersModel.setCurrentlyProcessedServerIndex(index) + ServersModel.currentlyProcessedIndex = index goToPage(PageEnum.PageSettingsServerInfo) menu.visible = false } diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index b98d2b8c..319399dc 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -29,6 +29,14 @@ PageType { } } + Connections { + target: ServersModel + + function onCurrentlyProcessedServerIndexChanged() { + content.isServerWithWriteAccess = ServersModel.isCurrentlyProcessedServerHasWriteAccess() + } + } + FlickableType { id: fl anchors.top: parent.top @@ -42,7 +50,10 @@ PageType { anchors.left: parent.left anchors.right: parent.right + property bool isServerWithWriteAccess: ServersModel.isCurrentlyProcessedServerHasWriteAccess() //todo make it property? + LabelWithButtonType { + visible: content.isServerWithWriteAccess Layout.fillWidth: true text: qsTr("Clear Amnezia cache") @@ -65,9 +76,12 @@ PageType { } } - DividerType {} + DividerType { + visible: content.isServerWithWriteAccess + } LabelWithButtonType { + visible: content.isServerWithWriteAccess Layout.fillWidth: true text: qsTr("Проверить сервер на наличие ранее установленных сервисов Amnezia") @@ -78,12 +92,14 @@ PageType { } } - DividerType {} + DividerType { + visible: content.isServerWithWriteAccess + } LabelWithButtonType { Layout.fillWidth: true - text: "Remove server from application" + text: qsTr("Remove server from application") textColor: "#EB5757" clickedFunction: function() { @@ -115,9 +131,10 @@ PageType { DividerType {} LabelWithButtonType { + visible: content.isServerWithWriteAccess Layout.fillWidth: true - text: "Clear server from Amnezia software" + text: qsTr("Clear server from Amnezia software") textColor: "#EB5757" clickedFunction: function() { @@ -142,7 +159,9 @@ PageType { } } - DividerType {} + DividerType { + visible: content.isServerWithWriteAccess + } QuestionDrawer { id: questionDrawer diff --git a/client/ui/qml/Pages2/PageSettingsServerInfo.qml b/client/ui/qml/Pages2/PageSettingsServerInfo.qml index 3f2562da..aa882aa5 100644 --- a/client/ui/qml/Pages2/PageSettingsServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsServerInfo.qml @@ -54,7 +54,13 @@ PageType { actionButtonImage: "qrc:/images/controls/edit-3.svg" headerText: name - descriptionText: credentialsLogin + " · " + hostName + descriptionText: { + if (ServersModel.isCurrentlyProcessedServerHasWriteAccess()) { + return credentialsLogin + " · " + hostName + } else { + return hostName + } + } actionButtonFunction: function() { serverNameEditDrawer.visible = true @@ -123,10 +129,14 @@ PageType { } TabButtonType { + visible: protocolsPage.installedProtocolsCount + width: protocolsPage.installedProtocolsCount ? undefined : 0 isSelected: tabBar.currentIndex === 0 text: qsTr("Protocols") } TabButtonType { + visible: servicesPage.installedServicesCount + width: servicesPage.installedServicesCount ? undefined : 0 isSelected: tabBar.currentIndex === 1 text: qsTr("Services") } @@ -143,9 +153,11 @@ PageType { currentIndex: tabBar.currentIndex PageSettingsServerProtocols { + id: protocolsPage stackView: root.stackView } PageSettingsServerServices { + id: servicesPage stackView: root.stackView } PageSettingsServerData { diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocols.qml b/client/ui/qml/Pages2/PageSettingsServerProtocols.qml index e93aa528..1d806d07 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocols.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocols.qml @@ -7,6 +7,7 @@ import SortFilterProxyModel 0.2 import PageEnum 1.0 import ProtocolEnum 1.0 import ContainerProps 1.0 +import ContainersModelFilters 1.0 import "./" import "../Controls2" @@ -17,22 +18,31 @@ import "../Components" PageType { id: root - SortFilterProxyModel { - id: containersProxyModel - sourceModel: ContainersModel - filters: [ - ValueFilter { - roleName: "serviceType" - value: ProtocolEnum.Vpn - }, - ValueFilter { - roleName: "isSupported" - value: true - } - ] - } + property var installedProtocolsCount SettingsContainersListView { - model: containersProxyModel + Connections { + target: ServersModel + + function onCurrentlyProcessedServerIndexChanged() { + updateContainersModelFilters() + } + } + + function updateContainersModelFilters() { + if (ServersModel.isCurrentlyProcessedServerHasWriteAccess()) { + proxyContainersModel.filters = ContainersModelFilters.getWriteAccessProtocolsListFilters() + } else { + proxyContainersModel.filters = ContainersModelFilters.getReadAccessProtocolsListFilters() + } + root.installedProtocolsCount = proxyContainersModel.count + } + + model: SortFilterProxyModel { + id: proxyContainersModel + sourceModel: ContainersModel + } + + Component.onCompleted: updateContainersModelFilters() } } diff --git a/client/ui/qml/Pages2/PageSettingsServerServices.qml b/client/ui/qml/Pages2/PageSettingsServerServices.qml index 7351f585..1e3d8722 100644 --- a/client/ui/qml/Pages2/PageSettingsServerServices.qml +++ b/client/ui/qml/Pages2/PageSettingsServerServices.qml @@ -7,6 +7,7 @@ import SortFilterProxyModel 0.2 import PageEnum 1.0 import ProtocolEnum 1.0 import ContainerProps 1.0 +import ContainersModelFilters 1.0 import "./" import "../Controls2" @@ -17,22 +18,31 @@ import "../Components" PageType { id: root - SortFilterProxyModel { - id: containersProxyModel - sourceModel: ContainersModel - filters: [ - ValueFilter { - roleName: "serviceType" - value: ProtocolEnum.Other - }, - ValueFilter { - roleName: "isSupported" - value: true - } - ] - } + property var installedServicesCount SettingsContainersListView { - model: containersProxyModel + Connections { + target: ServersModel + + function onCurrentlyProcessedServerIndexChanged() { + updateContainersModelFilters() + } + } + + function updateContainersModelFilters() { + if (ServersModel.isCurrentlyProcessedServerHasWriteAccess()) { + proxyContainersModel.filters = ContainersModelFilters.getWriteAccessServicesListFilters() + } else { + proxyContainersModel.filters = ContainersModelFilters.getReadAccessServicesListFilters() + } + root.installedServicesCount = proxyContainersModel.count + } + + model: SortFilterProxyModel { + id: proxyContainersModel + sourceModel: ContainersModel + } + + Component.onCompleted: updateContainersModelFilters() } } diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index a601199a..7f2b1688 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -89,8 +89,7 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { - ServersModel.setCurrentlyProcessedServerIndex(index) - ContainersModel.setCurrentlyProcessedServerIndex(index) + ServersModel.currentlyProcessedIndex = index goToPage(PageEnum.PageSettingsServerInfo) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index fa6b1f92..fc8bf9ae 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -79,6 +79,10 @@ PageType { text: qsTr("Set up a server the easy way") onClicked: function() { + if (!isCredentialsFilled()) { + return + } + InstallController.setShouldCreateServer(true) InstallController.setCurrentlyInstalledServerCredentials(hostname.textField.text, username.textField.text, secretData.textField.text) @@ -100,6 +104,10 @@ PageType { text: qsTr("Select protocol to install") onClicked: function() { + if (!isCredentialsFilled()) { + return + } + InstallController.setShouldCreateServer(true) InstallController.setCurrentlyInstalledServerCredentials(hostname.textField.text, username.textField.text, secretData.textField.text) @@ -108,4 +116,25 @@ PageType { } } } + + function isCredentialsFilled() { + var hasEmptyField = false + + if (hostname.textFieldText === "") { + hostname.errorText = qsTr("ip address cannot be empty") + hasEmptyField = true + } else if (!hostname.textField.acceptableInput) { + hostname.errorText = qsTr("Enter the address in the format 255.255.255.255:88") + } + + if (username.textFieldText === "") { + username.errorText = qsTr("login cannot be empty") + hasEmptyField = true + } + if (secretData.textFieldText === "") { + secretData.errorText = qsTr("password/private key cannot be empty") + hasEmptyField = true + } + return !hasEmptyField + } } diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index d0fdeabd..f1f5324e 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -61,7 +61,7 @@ PageType { function onServerAlreadyExists(serverIndex) { goToStartPage() - ServersModel.setCurrentlyProcessedServerIndex(serverIndex) + ServersModel.currentlyProcessedIndex = serverIndex goToPage(PageEnum.PageSettingsServerInfo, false) PageController.showErrorMessage(qsTr("The server has already been added to the application")) diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 504b0cb1..30430073 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -147,18 +147,28 @@ PageType { imageSource: "qrc:/images/controls/chevron-right.svg" - model: ServersModel - currentIndex: ServersModel.getDefaultServerIndex() + model: SortFilterProxyModel { + id: proxyServersModel + sourceModel: ServersModel + filters: [ + ValueFilter { + roleName: "hasWriteAccess" + value: true + } + ] + } + + currentIndex: 0 clickedFunction: function() { serverSelector.text = selectedText - ContainersModel.setCurrentlyProcessedServerIndex(currentIndex) + ServersModel.currentlyProcessedIndex = currentIndex protocolSelector.visible = true } Component.onCompleted: { serverSelector.text = selectedText - ContainersModel.setCurrentlyProcessedServerIndex(currentIndex) + ServersModel.currentlyProcessedIndex = currentIndex } } @@ -169,7 +179,7 @@ PageType { height: parent.height * 0.5 ColumnLayout { - id: header + id: protocolSelectorHeader anchors.top: parent.top anchors.left: parent.left @@ -187,12 +197,12 @@ PageType { } FlickableType { - anchors.top: header.bottom + anchors.top: protocolSelectorHeader.bottom anchors.topMargin: 16 - contentHeight: col.implicitHeight + contentHeight: protocolSelectorContent.implicitHeight Column { - id: col + id: protocolSelectorContent anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right @@ -265,7 +275,7 @@ PageType { } DropDownType { - id: connectionTypeSelector + id: exportTypeSelector property int currentIndex @@ -283,8 +293,6 @@ PageType { headerText: qsTr("Connection format") listView: ListViewType { - id: connectionTypeSelectorListView - rootWidth: root.width dividerVisible: true @@ -294,14 +302,14 @@ PageType { currentIndex: 0 clickedFunction: function() { - connectionTypeSelector.text = selectedText - connectionTypeSelector.currentIndex = currentIndex - connectionTypeSelector.menuVisible = false + exportTypeSelector.text = selectedText + exportTypeSelector.currentIndex = currentIndex + exportTypeSelector.menuVisible = false } Component.onCompleted: { - connectionTypeSelector.text = selectedText - connectionTypeSelector.currentIndex = currentIndex + exportTypeSelector.text = selectedText + exportTypeSelector.currentIndex = currentIndex } } } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 1307ee05..e2b83056 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -50,8 +50,7 @@ PageType { Component.onCompleted: { var pagePath = PageController.getPagePath(PageEnum.PageHome) - ServersModel.setCurrentlyProcessedServerIndex(ServersModel.getDefaultServerIndex()) - ContainersModel.setCurrentlyProcessedServerIndex(ServersModel.getDefaultServerIndex()) + ServersModel.currentlyProcessedIndex = ServersModel.defaultIndex tabBarStackView.push(pagePath, { "objectName" : pagePath }) } } @@ -65,8 +64,8 @@ PageType { topPadding: 8 bottomPadding: 8//34 - leftPadding: 96 - rightPadding: 96 + leftPadding: shareTabButton.visible ? 96 : 128 + rightPadding: shareTabButton.visible ? 96 : 128 background: Rectangle { border.width: 1 @@ -78,11 +77,25 @@ PageType { isSelected: tabBar.currentIndex === 0 image: "qrc:/images/controls/home.svg" onClicked: { - ContainersModel.setCurrentlyProcessedServerIndex(ServersModel.getDefaultServerIndex()) + ServersModel.currentlyProcessedIndex = ServersModel.defaultIndex tabBarStackView.goToTabBarPage(PageEnum.PageHome) } } TabImageButtonType { + id: shareTabButton + + Connections { + target: ServersModel + + function onDefaultServerIndexChanged() { + shareTabButton.visible = ServersModel.isCurrentlyProcessedServerHasWriteAccess() + shareTabButton.width = ServersModel.isCurrentlyProcessedServerHasWriteAccess() ? undefined : 0 + } + } + + visible: ServersModel.isCurrentlyProcessedServerHasWriteAccess() + width: visible ? undefined : 0 + isSelected: tabBar.currentIndex === 1 image: "qrc:/images/controls/share-2.svg" onClicked: { @@ -100,8 +113,8 @@ PageType { MouseArea { anchors.fill: tabBar - anchors.leftMargin: 96 - anchors.rightMargin: 96 + anchors.leftMargin: shareTabButton.visible ? 96 : 128 + anchors.rightMargin: shareTabButton.visible ? 96 : 128 cursorShape: Qt.PointingHandCursor enabled: false } From 795405c47d3c4fceffbe82aa6ce0be1e8970d12e Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 27 Jun 2023 19:07:42 +0900 Subject: [PATCH 036/278] added display of amnesia dns container activity on the main page --- client/resources.qrc | 1 + .../ui/controllers/connectionController.cpp | 2 + client/ui/controllers/settingsController.cpp | 26 +--- client/ui/controllers/settingsController.h | 6 +- client/ui/models/containers_model.cpp | 11 ++ client/ui/models/containers_model.h | 3 + client/ui/models/servers_model.cpp | 23 ++- client/ui/models/servers_model.h | 6 +- client/ui/qml/Components/ConnectButton.qml | 2 + .../qml/Components/ShareConnectionDrawer.qml | 1 - .../ui/qml/Components/ShowDetailsDrawer.qml | 10 ++ .../qml/Components/TransportProtoSelector.qml | 14 +- client/ui/qml/Controls2/DropDownType.qml | 3 - .../qml/Controls2/HorizontalRadioButton.qml | 6 + client/ui/qml/Pages2/PageHome.qml | 42 ++++-- .../Pages2/PageSettingsServerProtocols.qml | 3 +- .../qml/Pages2/PageSettingsServerServices.qml | 3 +- .../qml/Pages2/PageSetupWizardCredentials.qml | 6 +- .../PageSetupWizardProtocolSettings.qml | 139 ++++++++++++++++-- client/ui/qml/Pages2/PageShare.qml | 12 +- client/ui/qml/Pages2/PageStart.qml | 4 +- 21 files changed, 238 insertions(+), 85 deletions(-) create mode 100644 client/ui/qml/Components/ShowDetailsDrawer.qml diff --git a/client/resources.qrc b/client/resources.qrc index 9aba0a6b..b306c154 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -270,5 +270,6 @@ images/controls/telegram.svg ui/qml/Controls2/TextTypes/SmallTextType.qml ui/qml/Filters/ContainersModelFilters.qml + ui/qml/Components/ShowDetailsDrawer.qml diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index aace2857..7d1b6bed 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -83,11 +83,13 @@ void ConnectionController::onConnectionStateChanged(Vpn::ConnectionState state) } case Vpn::ConnectionState::Error: { m_isConnectionInProgress = false; + m_connectionStateText = tr("Connect"); emit connectionErrorOccurred(getLastConnectionError()); break; } case Vpn::ConnectionState::Unknown: { m_isConnectionInProgress = false; + m_connectionStateText = tr("Connect"); emit connectionErrorOccurred(getLastConnectionError()); break; } diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index b86b1cff..fcfcc7af 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -8,15 +8,10 @@ SettingsController::SettingsController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, - const std::shared_ptr &settings, - QObject *parent) - : QObject(parent) - , m_serversModel(serversModel) - , m_containersModel(containersModel) - , m_settings(settings) + const std::shared_ptr &settings, QObject *parent) + : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings) { - m_appVersion = QString("%1: %2 (%3)") - .arg(tr("Software version"), QString(APP_MAJOR_VERSION), __DATE__); + m_appVersion = QString("%1: %2 (%3)").arg(tr("Software version"), QString(APP_MAJOR_VERSION), __DATE__); } void SettingsController::setAmneziaDns(bool enable) @@ -79,21 +74,16 @@ void SettingsController::clearLogs() void SettingsController::backupAppConfig() { - Utils::saveFile(".backup", - tr("Backup application config"), - "AmneziaVPN", - m_settings->backupAppConfig()); + Utils::saveFile(".backup", tr("Backup application config"), "AmneziaVPN", m_settings->backupAppConfig()); } void SettingsController::restoreAppConfig() { - QString fileName = Utils::getFileName(Q_NULLPTR, - tr("Open backup"), - QStandardPaths::writableLocation( - QStandardPaths::DocumentsLocation), - "*.backup"); + QString fileName = + Utils::getFileName(Q_NULLPTR, tr("Open backup"), + QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.backup"); - //todo error processing + // todo error processing if (fileName.isEmpty()) return; diff --git a/client/ui/controllers/settingsController.h b/client/ui/controllers/settingsController.h index 77de424c..f961a37d 100644 --- a/client/ui/controllers/settingsController.h +++ b/client/ui/controllers/settingsController.h @@ -12,12 +12,10 @@ class SettingsController : public QObject public: explicit SettingsController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, - const std::shared_ptr &settings, - QObject *parent = nullptr); + const std::shared_ptr &settings, QObject *parent = nullptr); Q_PROPERTY(QString primaryDns READ getPrimaryDns WRITE setPrimaryDns NOTIFY primaryDnsChanged) - Q_PROPERTY( - QString secondaryDns READ getSecondaryDns WRITE setSecondaryDns NOTIFY secondaryDnsChanged) + Q_PROPERTY(QString secondaryDns READ getSecondaryDns WRITE setSecondaryDns NOTIFY secondaryDnsChanged) public slots: void setAmneziaDns(bool enable); diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index 7a6946f5..ecacc184 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -132,6 +132,17 @@ void ContainersModel::clearCachedProfiles() } } +bool ContainersModel::isAmneziaDnsContainerInstalled() +{ + return m_containers.contains(DockerContainer::Dns); +} + +bool ContainersModel::isAmneziaDnsContainerInstalled(const int serverIndex) +{ + QMap containers = m_settings->containers(serverIndex); + return containers.contains(DockerContainer::Dns); +} + QHash ContainersModel::roleNames() const { QHash roles; diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index ebf47497..7331ef22 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -51,6 +51,9 @@ public slots: void removeAllContainers(); void clearCachedProfiles(); + bool isAmneziaDnsContainerInstalled(); + bool isAmneziaDnsContainerInstalled(const int serverIndex); + protected: QHash roleNames() const override; diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 9df243fe..b427f15b 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -59,10 +59,14 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const case CredentialsLoginRole: return m_settings->serverCredentials(index.row()).userName; case IsDefaultRole: return index.row() == m_defaultServerIndex; case IsCurrentlyProcessedRole: return index.row() == m_currenlyProcessedServerIndex; - case HasWriteAccess: { + case HasWriteAccessRole: { auto credentials = m_settings->serverCredentials(index.row()); return (!credentials.userName.isEmpty() && !credentials.secretData.isEmpty()); } + case ContainsAmneziaDnsRole: { + QString primaryDns = server.value(config_key::dns1).toString(); + return primaryDns == protocols::dns::amneziaDnsIp; + } } return QVariant(); @@ -109,7 +113,12 @@ bool ServersModel::isDefaultServerCurrentlyProcessed() bool ServersModel::isCurrentlyProcessedServerHasWriteAccess() { - return qvariant_cast(data(m_currenlyProcessedServerIndex, HasWriteAccess)); + return qvariant_cast(data(m_currenlyProcessedServerIndex, HasWriteAccessRole)); +} + +bool ServersModel::isDefaultServerHasWriteAccess() +{ + return qvariant_cast(data(m_currenlyProcessedServerIndex, HasWriteAccessRole)); } void ServersModel::addServer(const QJsonObject &server) @@ -138,6 +147,13 @@ void ServersModel::removeServer() endResetModel(); } +bool ServersModel::isDefaultServerConfigContainsAmneziaDns() +{ + const QJsonObject server = m_servers.at(m_defaultServerIndex).toObject(); + QString primaryDns = server.value(config_key::dns1).toString(); + return primaryDns == protocols::dns::amneziaDnsIp; +} + QHash ServersModel::roleNames() const { QHash roles; @@ -147,6 +163,7 @@ QHash ServersModel::roleNames() const roles[CredentialsLoginRole] = "credentialsLogin"; roles[IsDefaultRole] = "isDefault"; roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed"; - roles[HasWriteAccess] = "hasWriteAccess"; + roles[HasWriteAccessRole] = "hasWriteAccess"; + roles[ContainsAmneziaDnsRole] = "containsAmneziaDns"; return roles; } diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index 6cb9859e..0ec78e7e 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -16,7 +16,8 @@ public: CredentialsLoginRole, IsDefaultRole, IsCurrentlyProcessedRole, - HasWriteAccess + HasWriteAccessRole, + ContainsAmneziaDnsRole }; ServersModel(std::shared_ptr settings, QObject *parent = nullptr); @@ -37,6 +38,7 @@ public slots: bool isDefaultServerCurrentlyProcessed(); bool isCurrentlyProcessedServerHasWriteAccess(); + bool isDefaultServerHasWriteAccess(); const int getServersCount(); @@ -46,6 +48,8 @@ public slots: void addServer(const QJsonObject &server); void removeServer(); + bool isDefaultServerConfigContainsAmneziaDns(); + protected: QHash roleNames() const override; diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml index 626c4ffb..1f27973a 100644 --- a/client/ui/qml/Components/ConnectButton.qml +++ b/client/ui/qml/Components/ConnectButton.qml @@ -17,6 +17,8 @@ Button { text: ConnectionController.connectionStateText + enabled: !ConnectionController.isConnectionInProgress + background: Item { clip: true diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index e03738c8..720e3206 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -76,7 +76,6 @@ DrawerType { } } - BasicButtonType { Layout.fillWidth: true Layout.topMargin: 8 diff --git a/client/ui/qml/Components/ShowDetailsDrawer.qml b/client/ui/qml/Components/ShowDetailsDrawer.qml new file mode 100644 index 00000000..2f6d2656 --- /dev/null +++ b/client/ui/qml/Components/ShowDetailsDrawer.qml @@ -0,0 +1,10 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import "../Controls2" +import "../Controls2/TextTypes" + +Item { + +} diff --git a/client/ui/qml/Components/TransportProtoSelector.qml b/client/ui/qml/Components/TransportProtoSelector.qml index 6f5aa44b..dd315deb 100644 --- a/client/ui/qml/Components/TransportProtoSelector.qml +++ b/client/ui/qml/Components/TransportProtoSelector.qml @@ -8,11 +8,9 @@ import "../Controls2/TextTypes" Rectangle { id: root - property var rootWidth: root.width + property real rootWidth: root.width property int currentIndex - property alias mouseArea: transportProtoButtonMouseArea - implicitWidth: transportProtoButtonGroup.implicitWidth implicitHeight: transportProtoButtonGroup.implicitHeight @@ -30,8 +28,6 @@ Rectangle { implicitWidth: (rootWidth - 32) / 2 text: "UDP" - hoverEnabled: !transportProtoButtonMouseArea.enabled - onClicked: { root.currentIndex = 0 } @@ -43,17 +39,9 @@ Rectangle { implicitWidth: (rootWidth - 32) / 2 text: "TCP" - hoverEnabled: !transportProtoButtonMouseArea.enabled - onClicked: { root.currentIndex = 1 } } } - - MouseArea { - id: transportProtoButtonMouseArea - - anchors.fill: parent - } } diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 38cd0afa..9b9c718a 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -19,7 +19,6 @@ Item { property string rootButtonImage: "qrc:/images/controls/chevron-down.svg" property string rootButtonImageColor: "#D7D8DB" property string rootButtonBackgroundColor: "#1C1D21" - property int rootButtonMaximumWidth: 0 property string rootButtonHoveredBorderColor: "#494B50" property string rootButtonDefaultBorderColor: "transparent" @@ -88,8 +87,6 @@ Item { horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter - Layout.maximumWidth: rootButtonMaximumWidth ? rootButtonMaximumWidth : implicitWidth - color: root.textColor text: root.text diff --git a/client/ui/qml/Controls2/HorizontalRadioButton.qml b/client/ui/qml/Controls2/HorizontalRadioButton.qml index 86218b3f..4a2a13dd 100644 --- a/client/ui/qml/Controls2/HorizontalRadioButton.qml +++ b/client/ui/qml/Controls2/HorizontalRadioButton.qml @@ -88,4 +88,10 @@ RadioButton { horizontalAlignment: Qt.AlignHCenter } } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + enabled: false + } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 9f121273..b97acad1 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -95,13 +95,20 @@ PageType { Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter text: { - var string = "" - if (SettingsController.isAmneziaDnsEnabled()) { - string += "Amnezia DNS | " + var description = "" + if (ServersModel.isDefaultServerHasWriteAccess()) { + if (SettingsController.isAmneziaDnsEnabled() + && ContainersModel.isAmneziaDnsContainerInstalled(ServersModel.getDefaultServerIndex())) { + description += "Amnezia DNS | " + } + } else { + if (ServersModel.isDefaultServerConfigContainsAmneziaDns) { + description += "Amnezia DNS | " + } } - string += root.currentContainerName + " | " + root.currentServerHostName - return string + description += root.currentContainerName + " | " + root.currentServerHostName + return description } } } @@ -153,7 +160,6 @@ PageType { rootButtonBorderWidth: 0 rootButtonImageColor: "#0E0E11" - rootButtonMaximumWidth: 150 //todo make it dynamic rootButtonBackgroundColor: "#D7D8DB" text: root.currentContainerName @@ -194,14 +200,6 @@ PageType { currentIndex: ContainersModel.getDefaultContainer() } } - - BasicButtonType { - id: dnsButton - - implicitHeight: 40 - - text: "Amnezia DNS" - } } Header2Type { @@ -277,7 +275,21 @@ PageType { Layout.fillWidth: true text: name - descriptionText: hostName + descriptionText: { + var description = "" + if (hasWriteAccess) { + if (SettingsController.isAmneziaDnsEnabled() + && ContainersModel.isAmneziaDnsContainerInstalled(index)) { + description += "AmneziaDNS | " + } + } else { + if (containsAmneziaDns) { + description += "AmneziaDNS | " + } + } + + return description += hostName + } checked: index === serversMenuContent.currentIndex diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocols.qml b/client/ui/qml/Pages2/PageSettingsServerProtocols.qml index 1d806d07..2b67afca 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocols.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocols.qml @@ -21,11 +21,12 @@ PageType { property var installedProtocolsCount SettingsContainersListView { + id: settingsContainersListView Connections { target: ServersModel function onCurrentlyProcessedServerIndexChanged() { - updateContainersModelFilters() + settingsContainersListView.updateContainersModelFilters() } } diff --git a/client/ui/qml/Pages2/PageSettingsServerServices.qml b/client/ui/qml/Pages2/PageSettingsServerServices.qml index 1e3d8722..282e7e9e 100644 --- a/client/ui/qml/Pages2/PageSettingsServerServices.qml +++ b/client/ui/qml/Pages2/PageSettingsServerServices.qml @@ -21,11 +21,12 @@ PageType { property var installedServicesCount SettingsContainersListView { + id: settingsContainersListView Connections { target: ServersModel function onCurrentlyProcessedServerIndexChanged() { - updateContainersModelFilters() + settingsContainersListView.updateContainersModelFilters() } } diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index fc8bf9ae..5a2f7d88 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -121,18 +121,18 @@ PageType { var hasEmptyField = false if (hostname.textFieldText === "") { - hostname.errorText = qsTr("ip address cannot be empty") + hostname.errorText = qsTr("Ip address cannot be empty") hasEmptyField = true } else if (!hostname.textField.acceptableInput) { hostname.errorText = qsTr("Enter the address in the format 255.255.255.255:88") } if (username.textFieldText === "") { - username.errorText = qsTr("login cannot be empty") + username.errorText = qsTr("Login cannot be empty") hasEmptyField = true } if (secretData.textFieldText === "") { - secretData.errorText = qsTr("password/private key cannot be empty") + secretData.errorText = qsTr("Password/private key cannot be empty") hasEmptyField = true } return !hasEmptyField diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index c0483df4..5ca75f6f 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -29,7 +29,6 @@ PageType { } FlickableType { - id: fl anchors.fill: parent contentHeight: content.height @@ -41,18 +40,17 @@ PageType { anchors.right: parent.right ListView { - // todo change id naming - id: containers + id: processedContainerListView width: parent.width - height: containers.contentItem.height + height: contentItem.height currentIndex: -1 clip: true interactive: false model: proxyContainersModel delegate: Item { - implicitWidth: containers.width - implicitHeight: delegateContent.implicitHeight + implicitWidth: processedContainerListView.width + implicitHeight: (delegateContent.implicitHeight > root.height) ? delegateContent.implicitHeight : root.height ColumnLayout { id: delegateContent @@ -72,8 +70,122 @@ PageType { Layout.fillWidth: true - headerText: "Установка " + name - descriptionText: "Эти настройки можно будет изменить позже" + headerText: qsTr("Installing ") + name + descriptionText: qsTr("protocol description") + } + + BasicButtonType { + id: showDetailsButton + + Layout.topMargin: 16 + Layout.leftMargin: -8 + + implicitHeight: 32 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#FBB26A" + + text: qsTr("More detailed") + + onClicked: { + showDetailsDrawer.open() + } + } + + DrawerType { + id: showDetailsDrawer + + width: parent.width + height: parent.height * 0.9 + + BackButtonType { + id: showDetailsBackButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.leftMargin: 16 + anchors.topMargin: 16 + + backButtonFunction: function() { + showDetailsDrawer.close() + } + } + + FlickableType { + anchors.top: showDetailsBackButton.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + contentHeight: { + var emptySpaceHeight = parent.height - showDetailsBackButton.implicitHeight - showDetailsBackButton.anchors.topMargin + + return (showDetailsDrawerContent.implicitHeight > emptySpaceHeight) ? + showDetailsDrawerContent.implicitHeight : emptySpaceHeight + } + + ColumnLayout { + id: showDetailsDrawerContent + + anchors.fill: parent + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + Header2Type { + id: showDetailsDrawerHeader + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: name + } + + TextField { + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.bottomMargin: 16 + + padding: 0 + leftPadding: 0 + height: 24 + + color: "#D7D8DB" + + font.pixelSize: 16 + font.weight: Font.Medium + font.family: "PT Root UI VF" + + text: qsTr("detailed protocol description") + + wrapMode: Text.WordWrap + + readOnly: true + background: Rectangle { + anchors.fill: parent + color: "transparent" + } + } + + Rectangle { + Layout.fillHeight: true + color: "transparent" + } + + BasicButtonType { + Layout.fillWidth: true + Layout.bottomMargin: 32 + + text: qsTr("Close") + + onClicked: function() { + showDetailsDrawer.close() + } + } + } + } } ParagraphTextType { @@ -96,15 +208,12 @@ PageType { Layout.fillWidth: true Layout.topMargin: 16 + headerText: "Port" } Rectangle { - // todo make it dynamic - implicitHeight: root.height - port.implicitHeight - - transportProtoSelector.implicitHeight - transportProtoHeader.implicitHeight - - header.implicitHeight - backButton.implicitHeight - installButton.implicitHeight - 116 - + Layout.fillHeight: true color: "transparent" } @@ -134,7 +243,9 @@ PageType { transportProtoSelector.currentIndex = ProtocolProps.defaultTransportProto(defaultContainerProto) port.enabled = ProtocolProps.defaultPortChangeable(defaultContainerProto) - transportProtoSelector.mouseArea.enabled = !ProtocolProps.defaultTransportProtoChangeable(defaultContainerProto) + var protocolSelectorVisible = ProtocolProps.defaultTransportProtoChangeable(defaultContainerProto) + transportProtoSelector.visible = protocolSelectorVisible + transportProtoHeader.visible = protocolSelectorVisible } } } diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 30430073..1e3d02e8 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -260,12 +260,12 @@ PageType { } function fillConnectionTypeModel() { - connectionTypesModel = [amneziaConnectionFormat] + root.connectionTypesModel = [amneziaConnectionFormat] if (currentIndex === ContainerProps.containerFromString("OpenVpn")) { - connectionTypesModel.push(openVpnConnectionFormat) + root.connectionTypesModel.push(openVpnConnectionFormat) } else if (currentIndex === ContainerProps.containerFromString("wireGuardConnectionType")) { - connectionTypesModel.push(amneziaConnectionFormat) + root.connectionTypesModel.push(amneziaConnectionFormat) } } } @@ -287,7 +287,7 @@ PageType { drawerHeight: 0.4375 visible: accessTypeSelector.currentIndex === 0 - enabled: connectionTypesModel.length > 1 + enabled: root.connectionTypesModel.length > 1 descriptionText: qsTr("Connection format") headerText: qsTr("Connection format") @@ -298,7 +298,7 @@ PageType { imageSource: "qrc:/images/controls/chevron-right.svg" - model: connectionTypesModel + model: root.connectionTypesModel currentIndex: 0 clickedFunction: function() { @@ -326,7 +326,7 @@ PageType { onClicked: { if (accessTypeSelector.currentIndex === 0) { - connectionTypesModel[connectionTypeSelector.currentIndex].func() + root.connectionTypesModel[accessTypeSelector.currentIndex].func() } else { ExportController.generateConfig(true) } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index e2b83056..76450b16 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -77,8 +77,8 @@ PageType { isSelected: tabBar.currentIndex === 0 image: "qrc:/images/controls/home.svg" onClicked: { - ServersModel.currentlyProcessedIndex = ServersModel.defaultIndex tabBarStackView.goToTabBarPage(PageEnum.PageHome) + ServersModel.currentlyProcessedIndex = ServersModel.defaultIndex } } TabImageButtonType { @@ -94,7 +94,7 @@ PageType { } visible: ServersModel.isCurrentlyProcessedServerHasWriteAccess() - width: visible ? undefined : 0 + width: ServersModel.isCurrentlyProcessedServerHasWriteAccess() ? undefined : 0 isSelected: tabBar.currentIndex === 1 image: "qrc:/images/controls/share-2.svg" From 464d77dfb58ae284cb2ae248b8a3541d2c8cc4a3 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 30 Jun 2023 10:40:43 +0900 Subject: [PATCH 037/278] added functionality to change app language via settings --- client/CMakeLists.txt | 18 +- client/amnezia_application.cpp | 46 +- client/amnezia_application.h | 21 +- client/resources.qrc | 3 +- client/settings.h | 131 +- client/translations/amneziavpn_ru.qm | Bin 25539 -> 0 bytes client/translations/amneziavpn_ru.ts | 4382 +++++++++++------ client/ui/controllers/settingsController.cpp | 2 +- client/ui/models/languageModel.cpp | 62 + client/ui/models/languageModel.h | 62 + .../ConnectionTypeSelectionDrawer.qml | 2 + .../qml/Components/SelectLanguageDrawer.qml | 137 + .../qml/Components/ShareConnectionDrawer.qml | 2 - .../ui/qml/Components/ShowDetailsDrawer.qml | 10 - client/ui/qml/Controls2/BackButtonType.qml | 1 + client/ui/qml/Controls2/BasicButtonType.qml | 8 +- client/ui/qml/Controls2/DividerType.qml | 4 + client/ui/qml/Controls2/DropDownType.qml | 2 - .../Controls2/TextTypes/ButtonTextType.qml | 5 +- .../Controls2/TextTypes/CaptionTextType.qml | 5 +- .../Controls2/TextTypes/Header1TextType.qml | 7 +- .../Controls2/TextTypes/Header2TextType.qml | 5 +- .../qml/Controls2/TextTypes/LabelTextType.qml | 5 +- .../Controls2/TextTypes/ListItemTitleType.qml | 5 +- .../Controls2/TextTypes/ParagraphTextType.qml | 5 +- .../qml/Controls2/TextTypes/SmallTextType.qml | 5 +- client/ui/qml/Pages2/PageSettingsAbout.qml | 2 - .../ui/qml/Pages2/PageSettingsApplication.qml | 9 +- client/ui/qml/Pages2/PageSettingsBackup.qml | 2 - .../ui/qml/Pages2/PageSettingsConnection.qml | 2 - client/ui/qml/Pages2/PageSettingsDns.qml | 2 - .../ui/qml/Pages2/PageSettingsServerInfo.qml | 4 +- .../ui/qml/Pages2/PageSettingsServersList.qml | 4 +- .../Pages2/PageSetupWizardConfigSource.qml | 9 +- .../qml/Pages2/PageSetupWizardCredentials.qml | 2 - client/ui/qml/Pages2/PageSetupWizardEasy.qml | 2 - .../PageSetupWizardProtocolSettings.qml | 4 +- .../qml/Pages2/PageSetupWizardProtocols.qml | 22 +- client/ui/qml/Pages2/PageSetupWizardStart.qml | 1 + .../ui/qml/Pages2/PageSetupWizardTextKey.qml | 6 +- .../qml/Pages2/PageSetupWizardViewConfig.qml | 2 - client/ui/qml/Pages2/PageShare.qml | 2 - 42 files changed, 3333 insertions(+), 1677 deletions(-) delete mode 100644 client/translations/amneziavpn_ru.qm create mode 100644 client/ui/models/languageModel.cpp create mode 100644 client/ui/models/languageModel.h create mode 100644 client/ui/qml/Components/SelectLanguageDrawer.qml delete mode 100644 client/ui/qml/Components/ShowDetailsDrawer.qml diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index f2346ec5..ad9a4cf4 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -19,15 +19,12 @@ set_property(GLOBAL PROPERTY AUTOMOC_TARGETS_FOLDER "Autogen") set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "Autogen") set(PACKAGES - Widgets Core Gui Network Xml - RemoteObjects Quick Svg QuickControls2 - Core5Compat Concurrent + Widgets Core Gui Network Xml + RemoteObjects Quick Svg QuickControls2 + Core5Compat Concurrent LinguistTools ) if(IOS) - set(PACKAGES - ${PACKAGES} - Multimedia - ) + set(PACKAGES ${PACKAGES} Multimedia) endif() find_package(Qt6 REQUIRED COMPONENTS ${PACKAGES}) @@ -38,11 +35,9 @@ set(LIBS ${LIBS} Qt6::Quick Qt6::Svg Qt6::QuickControls2 Qt6::Core5Compat Qt6::Concurrent ) + if(IOS) - set(LIBS - ${LIBS} - Qt6::Multimedia - ) + set(LIBS ${LIBS} Qt6::Multimedia) endif() qt_standard_project_setup() @@ -349,6 +344,7 @@ if(CMAKE_OSX_SYSROOT STREQUAL "iphoneos") endif() qt_add_executable(${PROJECT} ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC}) + qt_add_translations(${PROJECT} TS_FILES ${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ru.ts) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 7159aa4f..7c3b466b 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -3,12 +3,11 @@ #include #include #include +#include #include #include #include #include -#include - #include "core/servercontroller.h" #include "logger.h" @@ -88,6 +87,10 @@ void AmneziaApplication::init() connect(m_serversModel.get(), &ServersModel::currentlyProcessedServerIndexChanged, m_containersModel.get(), &ContainersModel::setCurrentlyProcessedServerIndex); + m_languageModel.reset(new LanguageModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get()); + connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &AmneziaApplication::updateTranslator); + m_configurator = std::shared_ptr(new VpnConfigurator(m_settings, this)); m_vpnConnection.reset(new VpnConnection(m_settings, m_configurator)); @@ -130,18 +133,16 @@ void AmneziaApplication::init() // m_uiLogic->showOnStartup(); // #endif -#endif - - // TODO - fix -#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) - if (isPrimary()) { - QObject::connect(this, &SingleApplication::instanceStarted, m_uiLogic, [this](){ - qDebug() << "Secondary instance started, showing this window instead"; - emit m_uiLogic->show(); - emit m_uiLogic->raise(); - }); - } -#endif +// // TODO - fix +// #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) +// if (isPrimary()) { +// QObject::connect(this, &SingleApplication::instanceStarted, m_uiLogic, [this](){ +// qDebug() << "Secondary instance started, showing this window instead"; +// emit m_uiLogic->show(); +// emit m_uiLogic->raise(); +// }); +// } +// #endif // Android TextField clipboard workaround // https://bugreports.qt.io/browse/QTBUG-113461 @@ -195,12 +196,27 @@ void AmneziaApplication::loadFonts() void AmneziaApplication::loadTranslator() { + auto locale = m_settings->getAppLanguage(); m_translator = new QTranslator; - if (m_translator->load(QLocale(), QString("amneziavpn"), QLatin1String("_"), QLatin1String(":/translations"))) { + if (m_translator->load(locale, QString("amneziavpn"), QLatin1String("_"), QLatin1String(":/i18n"))) { installTranslator(m_translator); } } +void AmneziaApplication::updateTranslator(const QLocale &locale) +{ + QResource::registerResource(":/translations.qrc"); + if (!m_translator->isEmpty()) + QCoreApplication::removeTranslator(m_translator); + if (m_translator->load(locale, QString("amneziavpn"), QLatin1String("_"), QLatin1String(":/i18n"))) { + if (QCoreApplication::installTranslator(m_translator)) { + m_settings->setAppLanguage(locale); + } + + m_engine->retranslate(); + } +} + bool AmneziaApplication::parseCommands() { m_parser.setApplicationDescription(APPLICATION_NAME); diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 893511b6..4e9cc348 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -20,17 +20,17 @@ #include "ui/controllers/pageController.h" #include "ui/controllers/settingsController.h" #include "ui/models/containers_model.h" +#include "ui/models/languageModel.h" #include "ui/models/servers_model.h" #define amnApp (static_cast(QCoreApplication::instance())) - #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) - #define AMNEZIA_BASE_CLASS QApplication + #define AMNEZIA_BASE_CLASS QApplication #else - #define AMNEZIA_BASE_CLASS SingleApplication - #define QAPPLICATION_CLASS QApplication - #include "singleapplication.h" + #define AMNEZIA_BASE_CLASS SingleApplication + #define QAPPLICATION_CLASS QApplication + #include "singleapplication.h" #endif class AmneziaApplication : public AMNEZIA_BASE_CLASS @@ -41,7 +41,8 @@ public: AmneziaApplication(int &argc, char *argv[]); #else AmneziaApplication(int &argc, char *argv[], bool allowSecondary = false, - SingleApplication::Options options = SingleApplication::User, int timeout = 1000, const QString &userData = {} ); + SingleApplication::Options options = SingleApplication::User, int timeout = 1000, + const QString &userData = {}); #endif virtual ~AmneziaApplication(); @@ -49,6 +50,7 @@ public: void registerTypes(); void loadFonts(); void loadTranslator(); + void updateTranslator(const QLocale &locale); bool parseCommands(); QQmlApplicationEngine *qmlEngine() const; @@ -58,14 +60,15 @@ private: std::shared_ptr m_settings; std::shared_ptr m_configurator; - ContainerProps* m_containerProps {}; - ProtocolProps* m_protocolProps {}; + ContainerProps *m_containerProps {}; + ProtocolProps *m_protocolProps {}; - QTranslator* m_translator; + QTranslator *m_translator; QCommandLineParser m_parser; QSharedPointer m_containersModel; QSharedPointer m_serversModel; + QScopedPointer m_languageModel; QSharedPointer m_vpnConnection; diff --git a/client/resources.qrc b/client/resources.qrc index b306c154..357cb40c 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -1,6 +1,5 @@ - translations/amneziavpn_ru.qm images/close.png images/settings.png images/favorites_disabled.png @@ -270,6 +269,6 @@ images/controls/telegram.svg ui/qml/Controls2/TextTypes/SmallTextType.qml ui/qml/Filters/ContainersModelFilters.qml - ui/qml/Components/ShowDetailsDrawer.qml + ui/qml/Components/SelectLanguageDrawer.qml diff --git a/client/settings.h b/client/settings.h index 28a49e18..9bf40ac7 100644 --- a/client/settings.h +++ b/client/settings.h @@ -2,15 +2,15 @@ #define SETTINGS_H #include -#include #include +#include -#include #include +#include #include -#include "core/defs.h" #include "containers/containers_defs.h" +#include "core/defs.h" #include "secure_qsettings.h" using namespace amnezia; @@ -22,13 +22,19 @@ class Settings : public QObject Q_OBJECT public: - explicit Settings(QObject* parent = nullptr); + explicit Settings(QObject *parent = nullptr); ServerCredentials defaultServerCredentials() const; ServerCredentials serverCredentials(int index) const; - QJsonArray serversArray() const { return QJsonDocument::fromJson(m_settings.value("Servers/serversList").toByteArray()).array(); } - void setServersArray(const QJsonArray &servers) { m_settings.setValue("Servers/serversList", QJsonDocument(servers).toJson()); } + QJsonArray serversArray() const + { + return QJsonDocument::fromJson(m_settings.value("Servers/serversList").toByteArray()).array(); + } + void setServersArray(const QJsonArray &servers) + { + m_settings.setValue("Servers/serversList", QJsonDocument(servers).toJson()); + } // Servers section int serversCount() const; @@ -37,9 +43,18 @@ public: void removeServer(int index); bool editServer(int index, const QJsonObject &server); - int defaultServerIndex() const { return m_settings.value("Servers/defaultServerIndex", 0).toInt(); } - void setDefaultServer(int index) { m_settings.setValue("Servers/defaultServerIndex", index); } - QJsonObject defaultServer() const { return server(defaultServerIndex()); } + int defaultServerIndex() const + { + return m_settings.value("Servers/defaultServerIndex", 0).toInt(); + } + void setDefaultServer(int index) + { + m_settings.setValue("Servers/defaultServerIndex", index); + } + QJsonObject defaultServer() const + { + return server(defaultServerIndex()); + } void setDefaultContainer(int serverIndex, DockerContainer container); DockerContainer defaultContainer(int serverIndex) const; @@ -61,13 +76,28 @@ public: QString nextAvailableServerName() const; // App settings section - bool isAutoConnect() const { return m_settings.value("Conf/autoConnect", false).toBool(); } - void setAutoConnect(bool enabled) { m_settings.setValue("Conf/autoConnect", enabled); } + bool isAutoConnect() const + { + return m_settings.value("Conf/autoConnect", false).toBool(); + } + void setAutoConnect(bool enabled) + { + m_settings.setValue("Conf/autoConnect", enabled); + } - bool isStartMinimized() const { return m_settings.value("Conf/startMinimized", false).toBool(); } - void setStartMinimized(bool enabled) { m_settings.setValue("Conf/startMinimized", enabled); } + bool isStartMinimized() const + { + return m_settings.value("Conf/startMinimized", false).toBool(); + } + void setStartMinimized(bool enabled) + { + m_settings.setValue("Conf/startMinimized", enabled); + } - bool isSaveLogs() const { return m_settings.value("Conf/saveLogs", false).toBool(); } + bool isSaveLogs() const + { + return m_settings.value("Conf/saveLogs", false).toBool(); + } void setSaveLogs(bool enabled); enum RouteMode { @@ -75,16 +105,29 @@ public: VpnOnlyForwardSites, VpnAllExceptSites }; - Q_ENUM (RouteMode) + Q_ENUM(RouteMode) QString routeModeString(RouteMode mode) const; - RouteMode routeMode() const { return static_cast(m_settings.value("Conf/routeMode", 0).toInt()); } - void setRouteMode(RouteMode mode) { m_settings.setValue("Conf/routeMode", mode); } + RouteMode routeMode() const + { + return static_cast(m_settings.value("Conf/routeMode", 0).toInt()); + } + void setRouteMode(RouteMode mode) + { + m_settings.setValue("Conf/routeMode", mode); + } - QVariantMap vpnSites(RouteMode mode) const { return m_settings.value("Conf/" + routeModeString(mode)).toMap(); } - void setVpnSites(RouteMode mode, const QVariantMap &sites) { m_settings.setValue("Conf/"+ routeModeString(mode), sites); m_settings.sync(); } - void addVpnSite(RouteMode mode, const QString &site, const QString &ip= ""); + QVariantMap vpnSites(RouteMode mode) const + { + return m_settings.value("Conf/" + routeModeString(mode)).toMap(); + } + void setVpnSites(RouteMode mode, const QVariantMap &sites) + { + m_settings.setValue("Conf/" + routeModeString(mode), sites); + m_settings.sync(); + } + void addVpnSite(RouteMode mode, const QString &site, const QString &ip = ""); void addVpnSites(RouteMode mode, const QMap &sites); // map QStringList getVpnIps(RouteMode mode) const; void removeVpnSite(RouteMode mode, const QString &site); @@ -92,33 +135,59 @@ public: void addVpnIps(RouteMode mode, const QStringList &ip); void removeVpnSites(RouteMode mode, const QStringList &sites); - bool useAmneziaDns() const { return m_settings.value("Conf/useAmneziaDns", true).toBool(); } - void setUseAmneziaDns(bool enabled) { m_settings.setValue("Conf/useAmneziaDns", enabled); } + bool useAmneziaDns() const + { + return m_settings.value("Conf/useAmneziaDns", true).toBool(); + } + void setUseAmneziaDns(bool enabled) + { + m_settings.setValue("Conf/useAmneziaDns", enabled); + } QString primaryDns() const; QString secondaryDns() const; - //QString primaryDns() const { return m_primaryDns; } - void setPrimaryDns(const QString &primaryDns) { m_settings.setValue("Conf/primaryDns", primaryDns); } + // QString primaryDns() const { return m_primaryDns; } + void setPrimaryDns(const QString &primaryDns) + { + m_settings.setValue("Conf/primaryDns", primaryDns); + } - //QString secondaryDns() const { return m_secondaryDns; } - void setSecondaryDns(const QString &secondaryDns) { m_settings.setValue("Conf/secondaryDns", secondaryDns); } + // QString secondaryDns() const { return m_secondaryDns; } + void setSecondaryDns(const QString &secondaryDns) + { + m_settings.setValue("Conf/secondaryDns", secondaryDns); + } static const char cloudFlareNs1[]; static const char cloudFlareNs2[]; -// static constexpr char openNicNs5[] = "94.103.153.176"; -// static constexpr char openNicNs13[] = "144.76.103.143"; + // static constexpr char openNicNs5[] = "94.103.153.176"; + // static constexpr char openNicNs13[] = "144.76.103.143"; - QByteArray backupAppConfig() const { return m_settings.backupAppConfig(); } - bool restoreAppConfig(const QByteArray &cfg) { return m_settings.restoreAppConfig(cfg); } + QByteArray backupAppConfig() const + { + return m_settings.backupAppConfig(); + } + bool restoreAppConfig(const QByteArray &cfg) + { + return m_settings.restoreAppConfig(cfg); + } + + QLocale getAppLanguage() + { + return m_settings.value("Conf/appLanguage", QLocale()).toLocale(); + }; + void setAppLanguage(QLocale locale) + { + m_settings.setValue("Conf/appLanguage", locale); + }; signals: void saveLogsChanged(); private: SecureQSettings m_settings; - }; #endif // SETTINGS_H diff --git a/client/translations/amneziavpn_ru.qm b/client/translations/amneziavpn_ru.qm deleted file mode 100644 index f0e3aaea97fb44a56c38e26996633d05308393e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25539 zcmcJ13w&HxdFPRR^%}{R?Z}DaILfu+B(l-Vl5ESeq*xlswvcSgk{pwe@Y|8*N*X+x zJDG=MnFMHB+OkP$2`SA|pa}%%E+srd``hj=VSfZD*@bL(Lw5_?Qc~!H-$J0_Q8qxj z|NlAnIrq$11~!f~nz`qE=R4o`o$q~S@ZTqQ{g>~)`***zch9H))#IP~>ro+UmW2?n z5@OSNA!_ds;x%^(aU_rT|BS!iEX29r5#mn#%$^Y9@?-ey$3nc}WB6>h*!-?P6=L)C z!ua625VyP_jQ?f`aiBpM|M;j7?Zcva>IXs`UKG`aq~=z2dI6 zCxkfhRdLT=Q;4}=5%+u&@26f7Yq!0E`EL?$?f9V(`}c}>?EfR+nG}y)?i1qZPsQVp zv;fZI;_+|4D8#-$QLnF^7w`LW5%At1K5*Z#5U>4~cgmRB3vp<<>VLcu&+iIc)H+rXEqe$xGA;Pc^4!u(qyPETxV`pZ{9$IeaXXJ048^-pffJa`hUdt%c^ zp8F!;*KhiAy&=RMU7P;bo1OvPZ`gdxQS$NW&FPij09`)6`At9kJG^e+{MO&ZI#oM1 zzjyW}A!cqjns35BF8q;UejNBz|AAqC>VCXFYxKPi?}adifAStY?=)UN_&FiWWh2e; zcRpl1Ajf~-c!1+;ZZzID@_-N%Pa2P9K7(<`j3?y0*BBqPKMDExpz--u?6dBX>Megg z2RiMl?tWng_@Aww{5tlz@3MM*?Jd=-FFz&3f6h=4ADA5}$jkzbDs!peAhrtKBT>*ycW z?0;z(^M6(ow?K!sAJ?RhwSZnP*1Y8(Aa@6TRP**vze9*K^)-L|{QDuN57vD4k1q*v z+xu#s+W~wJzoWM9nRkQUU#yMo0^W09sBL~4e3`zhc6a^Pa9%!9dpz|V_VsdY{4ase zncmv?SNZy4?d9g9LbUeQKKl3X1D_{rKRO6GY?-M2)LZu8ye!pz_J@bC{#@;!9Rq#D zt82e?!#9NJeWh;yQ{dyRuc;gQ23~LYX5IAX#_;^(b=KM=IG10ldvN`2kdJrPJ@G7_ zA9!!wzx&2Fg*g6T-9J^;3o-V5ef!cic>ja?i}$o+KiAi<{LKXD@OSlZ{=d%v?ymY@ znF0QDE%k5x!qY1*|Ee=`moM-V%8+^YW+&gn)?gL z-?8)Jmf#=h)7k&@pZhcQfRo$-%(O+oTQ-F@@dsoA`#vRzt z<%WB1eGL1UZdhN1KDnvC;r<mq_|VsI?#;hy`0zCtcgIH>p8P@!beYxgCm-4j za(=bpD?32{vy%-!{T%oZT0jjMM8-nqfX)!+L7VW35}xJ~Ivav}~FG4EFKr`YjJ_8pXa}+Varp4CvLk<-dQV06BViYxVb^ zhrW9M)=OXS0{z>!KCtN%pj+$KPuv7}T`z6@!W%HY^^vWA)BZKc%ktK5y^MYBd3fu0 zUYfyq`TW+Oo__-RaiXd215ZKk+}Bk1A-o=GZmRz)>@R+{Y5#|2h3NTM)68!{kH!CE zlQkO`Vr04Lp*GO@#(!@5t)2f9a%?ue=i_<2KHK!4TCmRcA2ogF(DTrr=bFBAgs=aY z65^gNa6+^QQ<%n0#;|eB7&0b}qj=YccO%BA5jSqH!w^w}pDm_&qL@ybiG>9#p9k$l z>~tcPxsb{v?X@aG>XeX7qZ@Ni;rDT)A26mQyko{$JRK1$A|otuS)@e5gS^``XO|N> z%UrZ`=EzFMx|~Xw3+a@VDd?6taK&ZfA}wr@5Es2Ab_JJ+r|rbW3JY9$A7@1tyU2(; z#I!i$EfU(t*{qehWBN>mWe#6)AG2Z^yGUX$Yho6^7sN%8_m*o3?g<108)of=i}?ym zc7qo>{@i{=?t6R7Te(#$XD(J2g-zmB9-dXN(s8i`ZjNa*W8s4kl^#!2M)2;aG3w2Z zy~-R(Ce3`RVCld#3+`zTfI!wn7bNeHG2l(E={8T#XQRUUFZj`S z|7gV_o5lwBj-8Fq-8nsOo|-#7WloOo&;cURZ=nO3uP zxlqUsbakz*t#z*TcG|h6uDO}6nelkXa$zOi*WHyb4Yl5jG0O$(QlZscurmc5nW5H$t!CHoV7{=Pw#>qM7Naj7R# zJWW$xX33*1^k znrtHDtt_|XqF%t`)of}7@;eWXwe{{0~(VW zO)63yn4Ykm#0~;L@GdTGb2qFtcXAB)4h8Ua?5CrrQ<;1rkxpAl-9iVhxKJMm6<**r z*FZZ9Foa|kws5c{Q!&>uP^*Pu5uiN6Lg_6)`za{MIe z*97?^xk4X`t8#R|Tz3>>h##NOv!XN(b47Yh#tsR41adlT942OR)lm?C08jA=eh=Xt zXL6qFS5d0_0yqtEHH_`Uy70H;9!>#XuQ7~wgibqo%$Sl`s!x?P5$aJ1A7?|fWAtgj z9nqYd9z5ethKw=5>IJ=9u?JhM!Y9h$Z;2^>N8u6&QY9?Epfo-kme5WBA_b24sWTa% zV1%=z+#X4}V|O z>;*fGr-fxRTbxg)7P_*z)M}z&nHQ~fGn+`|^5&A2v2yqb$_l1U`Dlqu((wi1J|(Su zr+KGcG#6m|66w56e_=7TRD|D+zcSWBA%&^<%bABCTvg@hL zl4)I{BE&kaoi;2bTmp_O*lLU`?x$iVDSZ2;zuvbh#x9t}yk)K+!mu(+7B2L>Vlke0hm(3?c zC=78}5*14@)q7T|WOzU*KI$MVQRUH$gV9jSlHu|gV^q}W^kWQ;IL1>Uxx%B&(osjO zx($f0Xmg{n_7>m0-r9+*=Q!y$;}Cm33L(cJ_Fpo=-K5Ica zshZC4v+`8M*g>VNG0qK1l*$~$Gb&Kme3SOXku>j?s$&3Zt5d{8%&3H^Qkee1r7}<) z2&p@{K2;)BoKmGaRZA6%{YYG?TqnQ>>`|N(Gx(G^LgC;qR~-jc_)+c#&+r+3hgI{W zF^spb%GYGO~1caT3% zg*{eIg{jl%s;^}PDrPEgCR5Y~#dLv6BWV?&c&J~b)=43P$z`kp4zaxom6bE8NtSFx zoEhkd0+db8T7y4hw#^qY%FgFg^J!~;r#X><%F06(B=T0i-7G9)CH!AlhBD#j`C_V& zz)(V#`n1!W;~4iX2QMb_SRUGBC5voClK63ca2t80hFjOHd9#p9a3|(UB7YIuskJi} z3xmuTv(jl)DpRMuutG@D=Ar$l=+4bxbkZ{0Kov8;Y!}l>Go2`AK*8Qz5-LAeST}8C z81m57(9c9s=~T0WxfCxIfCu}vO7O?O1SKe~D}=Eh0!|;>%^m4{52ZjUxSoQ8`0)V1 z3&A0xEgzGIksdfz8jmwQqi(1+C^SzM)AG`iQ{5@GLUk7u|6v?RW$h2~@IpyUL3!}7 zLPWxf zHEuMR-q0V!ZeUORs5(wdwW>TZ7akRY^!-RxsZL8xjq+-UHGNllzOG_KRDju}GYtZj zM3nN_hlJ{=A4l=ir&Lq|T+Hj0s+Ef31aLhnxzUWz;&?)Cb2g2PGFyC4qX?sc-^?E# zld6gDdhn0_6bvEW1$Qs`fwA0w2WCjKgfZM1S2~D!Qo+d)KE;)XT1u5b))Mnb2d+mL z@abI7OPYx50G~b{C4;`U@^@*{^ahT~p9V%sJmJdOO4*J9&mlae@5=Q;hQyiUd+zX4 zF{Yd`aScUzb3nRyp1S18UeHaQ2Ng9a?oq-6@gZ|8QW`-SX)V>OU;Gv}?ejbwp#Ar}*GL6uaeEt3YuhH-=H#)hWT7AzVaW6QB>p2f-+ z7%wDkD=+=#pTpX8nv<55mFz5LlZYO)4zu_kG};-+6J0Ll_$o(|QmmXz6#*$-#uFr6 zA33&kOo<~awZc^7M9A!nqm6i86lA4+-CRH*Cjt!|bo;Ya{L-$*kKM@MPIG)AkZv3P=m%FzH`N6Ya{dbS@xSPK=_Q2RIcp%_<)2G?jlK zr+I8kWfsy!n0hB>TY%#MM+d=@mKRm8`z7*1@y3m`1KJaGA=QIaf!&kZtskGNc~j8R zO7}aiy{BQ#fD7*OBE$tp%U+QW;Ob4#chqjG9>wUL799lw8Mu^69POqGhp;X%<~*6t z0}NX5Nyj3pF)scL#RzpU!1+JjAm^78tB8K#CFYTiH?vl5B?X@piE#=5Ls{~lPOQ|I zZeT9eMMxeTF^~@!@X8`fKp6JMrj<(^5IsAClpB_rn>jx_H$KMYa$2cD$Z#Vpo|`># zLl1fe3~yANHKk)T-d+I5 zqJQ8tk-lgS0isKV6tFivHp1yhkLI*x#(r;dhvOg8n;Vc?jdDo*I`B!v8}%00=+gZPqBFC8fyRHZsR z!tEVp4aXciGYji~Z_&yZ@+M3olUT?l@kmA15AA4MeB|`_)Kuk)L9?+N=l&GX7)B(} zkKi3T!b4$8Jh6aNy#VtIjT~7H+a1H1LTUxGgNYewQDFa&FNB0B%f`(QFmjL{KhIk) z2tto}ueaW=cp^iugF&4#HH&r{0r1|)(xIJhk1r?ie@}PEw4Gi**xP+5GG3lls!>-J zy2)dWi|C;PxE46T#b0rCK!DD;$$ zW@18#i%P#tdEjq|lOJxVyFeBp8dj-5-I{%f#>iV4dpP_EST)Ie_$wlm5cF1SRjWD5 zcrj{7mpWmR574T!f-21^Q+X@y zbXQdQrPv^l}j;@kTB0}$Erky+BgycRHdv~KrwrL6yY)T)Ww*_57CG<4yKkQHyDfr(l{xYje% zL1FIpCN|JK&q_yC>mcrqoMB`~6ig3BBVkn5G^IvERVAo-l(9JE0c#qw(nur#URq`o z^ki%SYn;;oQi2j8jSw44=R);BO}qdsaZ2o zEG+BbHM?dTn7VGHPsZ4IdA||9#I?G~!f&~1@TU*x79WL4rolgmEis`62TJ~Cm^Ubs zw7M#ZUIg35b2)e@_QFCj$Amr7DM+Azf3mL0M7pxwQS%UTqGAdR8-;#8jQ!SvZZ8cO zbYOgovtKQx5Or#{#M6;ER$wc*GwnFZw6jQEN}9PgSe*pcjNBA580(3|&hbkNRu&}? z=Um8SnD)rtD($+g0|HjREQNC)<$^?G{8CCwvKDA7*~SMj(b8SNhuW4CiHjDh!1Ly8 znf|_8(Ng2q6DR~~lfHpc`uV5_@tPA52o{KZifXso7J$1kGVUPcE7NeF!Q?W_3(7O{ zQxpLi_RuBJB{xy2gjFYkZJ?(wWUjaDI$yL4PB(@Jxap+R5a1jK9n@XMHK3^UB!M#X z*Jn%Zrvs(D{j4!=0gO}Ly2hzQUaNFErED>tWqe2%KQ3jNKGKj3lvrjMf-l9-egARi zDYW;Q3oLcz^2@2LnYLD~v~D|XI323Iqf1gL|0wK-Z9RnyEi|u_r_lkuWOM;lb(k4N zP+9SSoRsk#Eju;*2)3lyUAie|@>Q>pI!M&F><>}-EY=Iy{Zi5pKtIN#XxD`K`XVZr zu9-k58ZtpBYohgoZ6FKl%u2f1=}uF#i>l*t7wlB&k$7`Wl?|d9m2NCCkvmZgV&%x) z3nhf_LkxnXu=|EPVLga27|#W(HmY~ZRY|M_(E}*W-;!B;#@U)`7M^h>a>1v&zOF{Y z(Px}fbtj6LvlesK+(WrarJBcV;Bkyo#drSAC|%Z20^IoM#BD2Rpk1ZX2H6+vVy2LT z4QEn#StqHT=IKNR#o9#%)1h)hUgibi>*s^D2C&tyccZTD@f~gwT4{1tIwcJ}=C#w{ zse(l+6Rgby0O+Kl`^wTF<$MG*U?4ybcw_Q#BgRu3F%V=1WMfiMp7V-xUV;dGbd{(p zHI!t^C9^8Tk&YoN6-@S>L7sV|vO^iBOM=rXp)f0PIvH6yfP7n$gbIy&&!E_tSo86Or4d!fF# zISMy5@fAHTj3JY5he~i$DI}9J?R0;S>!5fltbbxr*2km@Lp=-K=gSGu*yYvcd83rOdX=HrxzUeWm$ELbwiJVb;`_)feBs!|{ygA!7$vY&gMnPq^>NXzkD-SQrjNNR7lGb8) zGIl^$NZ(@@bM_bwoRqe{9lP(AP8anzjGmt)_K@m#HYZ(-Ktotec`8oJn$vBjlE>L} z5obk9jR^88;-H+BW}>&d3**4CCs&_~zJyW{TV+)TP1u{0*9|}dCAm%o0Gll7>X#ko zmdFLSkH6bI|1wIxZn57Z(6EaqdkalPfi`l{o?k5HRgrSP8;gTzh$zvhQvP-Wgl7J# zHx;X}DXnaH==X%s4>0)ZP-e;Q7vh75&|ViICw;LolKougtdg492hE|H{oR08c`hmw zuUtCcE!Y91(KOGj%VH{%%A=a3k^jwqD1kCQW}NNbPZ)-sX1j6e4Fq}y{{4d<*Q#T- ziV$f77>V)VIib+zWl{o%jS^4E|Cx7(=q-j!7ex0c8S&wNLsIq~RI4o*KDrDfcB1TY(&O8O2xV zU0_8|f!&ylyhv}=2NdypJ^aF*w}cd~o$!>DyWiyF?Vb(ZtzznonD8m-1$iYl;yjw_ z$rUCeX*?s|z8|Gmz3;5-dq||wK^MAX0#DFV33BTC74}%SM)P{D(rlth&kM}i9vJTN zW4vAPikOJaoDheWVqw{~(fT8s#B7O4yNZAKP&Nq?Hu6}=9H7ncqT>YnF%r;J@bL#- zXVo5vyhFaAF7d1+a3>c@2Soq#aO5mFKHN+mUE*kAFYOy-Z=%yh&V`-sLyUI76Rw$9 zWb-@8;>R#1S}%}okebn7Si(R~BWbU|!nd2}XQp^X(hsiMc`3)WnP|DR)2rAb7W0Q_ zBh^M6FSowvSE%ZyE7TCT2B!qd5cTdPZ4qKnp5wzgY5zPd}N~X4@IC-T^Pt3;@KF|uzbwRgtL&nug)lqFgoaD_Kr6;{~wYq?;V@ewc ztJ)+2SrAl+XVD%;`|Y(*c&O|^S_9t|Qi5{Z3{sAW z-=P4@BpySl17r5o$iWC@LqlpXMAs)78dQftz5G^#%COY1-u}Lg47*|%Co#JsEDq8^ zLYR9POdE!%sTK^EZ7L(+-Utu%(z;ig8*pV!Dm z+?cfd3Inwbc&q>@0_(PHyxMb9iK!>f2jdZ#g0hWSdzF5XhLj=4L;IU^=yuA zLy;IEtlI+hf$g)Z;okR8b?|OQ2P@H1w-B5PKnYC5EFR@usIZrbC5!i0bx^fAkf06@ z;MwHhSt$jw6>Rh@XTbKp{31fw2*-9#Xf=6?5_{X;h+)K_;4L zN}BLmby)S`@|J(e-i6L2iW?0_%R_!evt^DKc-@u_H(w^#X5}ohV3=#?$Fy9d_n_{- zc%DP}-U%|L!5a?ddH2h-n5%L8W!xuL7f;IEd1!Hz0dkj!)CbS2<-o{F z2CEk5JeX-}mk+FOH*VS%s+sBYR+x++v3lrO2;* zaL>2S{J{wAeb~dnynvf(@~f-Zf$Q5r6F1=#=HLGM!;$TW&Ur(^%FC}~Tv}M!$Vhy3 z;>JeuTMn-{^4f)E+6uP~yX z-x}K0rBllx?CR07nM-GFJKDk=8h70#k5^a0;Ont%o~llKhf%mB6WJg5Q+gGH$B15x zbC*Td0sUBmJz_ARVVmV~caLkJmaR03>hj_Rzd6q04Gk_{d+zM~>k+ogZ;Jj!V_qkQ zypT%b=&J9Y{$+Z>QSeuDRjuKUY{s>H61SiCu37}p4yP~5{R+yU7vAXKTvC2zWcKdJ z?C6ELZ9Wvg=RX3e5s6O2x8K%2rNaNiM)!@TUWj6aUnZTeuNXpvtnTr{u4Ivd)3eOH~dTU LCgW%Av1|SxmH|-1 diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index befce5d5..b1e7817f 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -2,1145 +2,2481 @@ - MainWindow + AdvancedServerSettingsLogic - - Connect to the already created VPN server - Подключиться к уже созданному серверу VPN - - - - Connection code - Код для подключения - - - - Connecting... - Подключение... - - - - - - - - Connect - Подключиться - - - - - Set up your own server - Настроить собственный сервер - - - Connect your server to use VPN - Подключите ваш сервер, чтобы использовать VPN - - - - Server IP address - IP адрес сервера - - - - Login to connect via SSH - Логин для подключения по SSH - - - - - - Password - Пароль - - - - Where to get connection data → - Где взять логин для подключения → - - - - vpn://... - - - - - - - - - Please wait, configuring process may take up to 5 minutes - Пожалуйста подождите, настройка может занять до 5 минут - - - - - Setup your server to use VPN - Настроить ваш сервер для VPN - - - - root - - - - - - Connect using SSH key - Использовать SSH ключ - - - Select VPN protocols to install - Выберите VPN протоколы для установки - - - - OpenVPN and ShadowSocks - with masking using Cloak plugin - OpenVPN и ShadowSocks - с маскировкой плагином Cloak - - - - Port (TCP) - Порт (TCP) - - - - 443 - - - - - - Fake Web Site - Сайт маскировки - - - - - mail.ru - - - - - ShadowSocks - - - - - Port(TCP) - Порт (TCP) - - - - 6789 - - - - - Encryption - Шифрование - - - - chacha20-ietf-poly1305 - - - - - xchacha20-ietf-poly1305 - - - - - - - aes-256-gcm - - - - - - aes-192-gcm - - - - - - - aes-128-gcm - - - - - OpenVPN - - - - - - - - Port - Порт - - - - Protocol - Протокол - - - - DNS settings - Настройки DNS - - - - UDP - - - - - TCP - - - - - AmneziaVPN will install OpenVPN protocol with public/private key pairs generated on server and client sides. You can also configure connection on your mobile device by copying exported ".ovpn" file to your device and setting up official OpenVPN client. We recommend do not use messengers for sending connection profile - it contains VPN private keys. - AmneziaVPN установит протокол OpenVPN и сгенерирует публичные/приваные пары ключей для серверной и клиентской стороны. Вы сможете так же настроить подключение для вашего мобильного устройства, экспортировав конфиг ".ovpn" на устройство и установив официальный клиент OpenVPN. Мы рекоммендуем не передавать конфиг через мессенджеры - он содержит приватный ключ для VPN. - - - - - - - - - - Configuring... - Настройка... - - - - Setup server - Установить сервер - - - - - 0 Mbps - 0 Мбит/сек - - - Add site - Добавить сайт - - - - Connected - Подключено - - - - How to use VPN - Как использовать VPN - - - - For all connections - Для всех соединений - - - - For selected sites - Для выбранных сайтов - - - - Error text - - - - - List of the most popular prohibited sites - Список самых популярных запрещенных сайтов - - - For example, rutor.org or 17.21.111.8 - Например, rutor.org или 17.21.111.8 - - - Anyone who logs in with this code will have the same rights to use the VPN as you. To create a new code, change your login and / or password for connection in your server settings. - Тот, кто зайдёт с этим кодом, будет иметь те же права на использование VPN, что и вы. Чтобы создать новый код смените логин и/или пароль для подлючения в настройках вашего сервера. - - - These sites will open via VPN - These sites will open via VPN - - - Delete selected item - Удалить выбранный элемент - - - Hostname or IP address - Имя хоста или IP адрес - - - - + - + - - - - Server settings - Настройки сервера - - - - Share connection - Поделиться подключением - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Lato'; font-size:8.25pt; font-weight:400; font-style:normal;"> -<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> + + + Clear server from Amnezia software - - Configure VPN protocols manually - Выбрать протоколы + + Service: + - - Run Setup Wizard - Мастер настройки - - - - If you want easily configure your server just run Wizard - Для облегченной настройки сервера запустите мастер настройки - - - - Press configure manually to choose VPN protocols you want to install - Выбрать протоколы для установки самостоятельно - - - - - - - - Setup Wizard - Мастер настройки - - - - High censorship level - Высокий уровень цензуры - - - - Medium censorship level - Средний уровень цензуры - - - - Low censorship level - Низкий уровень цензуры - - - - I'm living in country with high censorship level. Many of foreign web sites and VPNs blocked by my government. I want to setup reliable VPN, which is invisible for government. - Я живу в стране с высоким уровнем цензуры. Многие зарубежные сайты и VPN сервисы заблокированы. Я хочу установить надёжный VPN, невидимый для надзорных органов. - - - - I'm living in country with medium censorship level. Some web sites blocked by my government, but VPNs are not blocked at all. I want to setup flexible solution. - Я живу в стране со средним уровнем цензуры. Некоторые зарубежные сайты заблокированы, но VPN сервисы в целом работают. Я хочу установить гибкое решение. - - - - I just want to improve my privacy in internet. - Я просто хочу повысить уровень моей приватности в интернете. - - - - - - Next - Далее - - - - AmneziaVPN will install VPN protocol which is not visible for your internet provider and government firewall. Your VPN connection will be detected by your provider as regular web traffic to particular web site. - -You SHOULD set this web site address to some foreign web site which is not blocked by your internet provider. Other words you need to type below some foreign web site address which is accessible without VPN. - -Please note, this protocol still does not support export connection profile to mobile devices. Keep for updates. - AmneziaVPN установит VPN протокол невидимый для вашего провайдера и гос. фаервола. Ваше VPN соединение будет определяться как обычные запросы к определнному web сайту. -Вы ДОЛЖНЫ установить адрес этого сайта таким, который не заблокирован вашим провайдером, и находится за границей. Другими словами, вы должны ввести адрес какого-либо зарубежного сайта, который доступен и без VPN. - -Заметьте, этот протокол пока не имеет функции экспорта настроек подключения для мобильных устаройств. Следите за обновлениями. - - - - OpenVPN over Cloak (VPN obfuscation) profile will be installed - Будет установлен профиль OpenVPN over Cloak (VPN маскировка) - - - - Type web site address for mask - Адрес сайта для маскировки - - - - Optional. - -We recommend to enable VPN mode "For selected sites" and add blocked sites you need to visit manually. If you will choose this option, you will need add every blocked site you want to visit to the access list. You may switch between modes later. - -Please note, you should add addresses to the list after VPN connection established. You may add any domain, URL or IP address, it will be resolved to IP address. - Опционально. - -Мы рекомендуем включить режим VPN "Для выбранных сайтов" и добавить вручную заблокированные сайты, которые вы хотите посещать. Если вы выберите эту опцию, вы должны будете добавить каждый заблокированный сайт, который вы хотите посетить в список. Позже вы сможете переключаться между режимами работы VPN. - -Мы рекомендуем добавлять сайты в список только после того, как соединение VPN будет подключено. Вы сможете добавить любые домены, URL или IP адреса. - - - - - Start configuring - Начать настройку - - - - Turn on mode "VPN for selected sites" - Включить режим -"VPN для выбранных сайтов" - - - - AmneziaVPN will install VPN protocol which is difficult to detect by your internet provider and government firewall (but possible). In most cases, this is the most suitable protocol. This protocol is faster compared to the VPN protocols with "web traffic masking". - -This protocol support export connection profile to mobile devices using QR code (you should launch 3rd party opensource VPN client - ShadowSocks VPN). - AmneziaVPN установит VPN протокол, который трудно детектировать интернет провайдерам (но возможно). В большинстве случаев, это наиболее подходящий выбор. Этот протокол быстрее, по сравнению с VPN протоколами с полной маскировкой трафика. - -Этот протокол поддерживает экспорт профиля подключения с помощью QR кода для настройки на мобильных устройствах (вам нужно будет установить сторонний клиент VPN - ShadowSocks VPN). - - - - OpenVPN over ShadowSocks profile will be installed - Будет установлен профиль -OpenVPN over ShadowSocks - - - - OpenVPN profile will be installed - Будет установлен профиль OpenVPN - - - - Please wait. - Пожалуйста, ждите. - - - - Select VPN protocols - Выберите протоколы - - - - udp - - - - - tcp - - - - - + Add site - + Добавить сайт - - - - These sites will be opened using VPN - Эти сайты будут открываться через VPN - - - For example, yousite.com or 17.21.111.8 - Например, yousite.com или 17.21.111.8 - - - Web site or hostname or IP address - Web-сайт, имя хоста или IP-адрес - - - - - Reinstall server, clear server - Переустановить сервер, очистить сервер - - - - Server management - Управление сервером - - - - - Exit - Выход из приложения - - - - Auto start, Auto connect - Авто-запуск, авто-соединение - - - - App settings - Настройки приложения - - - - Network settings - Настройки сети - - - - Servers - Список серверов - - - - Add or import new server - Добавить или импортировать новый сервер - - - - Add server - Добавить сервер - - - - Servers list - Список серверов - - - - Auto start - Авто старт - - - - Application Settings - Настройки приложения - - - - Auto connect - Авто соединение - - - - Check for updates - Проверить обновления - - - - Start minimized - Запускать свёрнутым - - - - Open logs folder - Открыть папки с логами - - - - DNS Servers - DNS сервера - - - - - Reset to default value - Сбросить по умолчанию - - - - Primary DNS server - Первичный DSN сервер - - - - Secondary DNS server - Вторичный DNS сервер - - - - - Clear client cached profile - Удалить закешировнный профиль - - - - - Clear server from Amnezia software - Очистить сервер от Amnezia - - - - Forget this server - Забыть этот сервер - - - - root@yourserver.org - - - - - VPN protocols - VPN протоколы - - - - VPN Protocol: - VPN протокол: - - - - Protocols - Протоколы - - - - Cloak container - Cloak контейнер - - - - - - OpenVPN settings - Настройки OpenVPN - - - - - ShadowSocks settings - Настройки ShadowSocks - - - - Cloak settings - Настройки Cloak - - - - ShadowSocks container - ShadowSocks контейнер - - - - OpenVPN container - OpenVPN контейнер - - - - Full access - Полный доступ - - - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Consolas'; font-size:20px; font-weight:600; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:20pt;">vpn:\\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</span></p></body></html> - - - - - Anyone who logs in with this code will have the same permissions to use VPN and your server as you. -This code includes your server credentials! -Provide this code only to TRUSTED users. - Любой, кто получит этот код, получит полный доступ к серверу и использованию VPN. Этот код содержит пароль от сервера. -Предоставляйте этот код только доверенным пользователям. - - - - - - - Share for Amnezia client - Расшарить для Amnezia - - - - Anyone who logs in with this code will be able to connect to this VPN server. -This code does not include server credentials. - Любой, кто получит этот код, получит возможность подключаться к этому VPN серверу -Этот код не содержи пароль от сервера. - - - - - - - Generate config - Сгенерировать конфиг - - - - - Share for OpenVPN client - Расшарить для OpenVPN - - - - - Save file - Сохранить файл - - - - AmneziaVPN - - - - - Except selected sites - Кроме выбранных сайтов - - - - yousite.com or IP address - - - - - Web site/Hostname/IP address/Subnet - Web сайт/хост/IP адрес/подсеть - - - - Delete selected - Удалить выбранные - - - - Software version: X.X.X (01.06.2021) - - - - - Share Server (FULL ACCESS) - Расшарить сервер (FULL ACCESS) - - - - - Share for ShadowSocks client - Расшарить для ShadowSocks - - - - - Server: - Сервер: - - - - - Encryption: - Шифрование: - - - - - Port: - Порт: - - - - Password: - Пароль: - - - - Connection string - Строка подключения - - - - Share for Cloak client - Расшарить для Cloak - - - - OpenVPN Settings - Настройки OpenVPN - - - - Hash - Хеш - - - - - - Cipher - Шифр - - - - Network protocol - Сетевой протокол - - - - AES-256-GCM - - - - - AES-192-GCM - - - - - AES-128-GCM - - - - - AES-256-CBC - - - - - AES-192-CBC - - - - - AES-128-CBC - - - - - ChaCha20-Poly1305 - - - - - ARIA-256-CBC - - - - - CAMELLIA-256-CBC - - - - - none - - - - - VPN Addresses Subnet - Подсеть для VPN - - - - - - Save and restart VPN - Сохранить и перезапустить VPN - - - - Auto-negotiate encryption - Авто-согласование шифрования - - - - SHA512 - - - - - SHA384 - - - - - SHA256 - - - - - SHA3-512 - - - - - SHA3-384 - - - - - SHA3-256 - - - - - whirlpool - - - - - BLAKE2b512 - - - - - BLAKE2s256 - - - - - SHA1 - - - - - Block DNS requests outside of VPN - Блокировать запросы DNS мимо VPN - - - - Enable TLS auth - Включить TLS auth - - - - ShadowSocks Settings - Настройки ShadowSocks - - - - - chacha20-poly1305 - - - - - Cloak Settings - Настройки Cloak - - - - plain - - - - - - - - - - - - Copy - Копировать - - - - Cannot open logs folder! - Невозможно открыть папку с логами! - - - - - Please fill in all fields - Пожалуйста, заполните все поля - - - - It's public key. Private key required - Это публичный ключ. Требуется приватный - - - - of - из - - - - - - Error occurred while configuring server. - Ошибка во время настройки сервера - - - - Amnezia server installed - Amnezia сервер установлен - - - - Operation finished - Операция завершена - - - + Uninstalling Amnezia software... - Удаление Amnezia... + - + + Error occurred while cleaning the server. + + + + + + Error message: + + + + + See logs for details. - Смотрите логи для подробных деталей + - + Amnezia server successfully uninstalled - Amnezia сервер удален + - - Show - Показать окно + + Error occurred while scanning the server. + - - - Disconnect - Отключиться + + All containers installed on the server are added to the GUI + - - Visit Website - Посетить сайт + + No installed containers found on the server + + + + + AppSettingsLogic + + + Software version + - - Quit - Выход + + Save log + - - Do you really want to quit? - Вы действительно хотите выйти? + + Open backup + - - Import IP addresses - Импортовать IP адреса + + Can't import config, file is corrupted. + + + + + ClientInfoLogic + + + Service: + + + + + ClientManagementLogic + + + Service: + - - Import connection - Импортировать соединение + + An error occurred while getting the list of clients. + + + + ConnectionController - - Private key - Приватный ключ - - - - Connect using SSH password - Соединиться с паролем SSH - - - - Cache cleared - Кеш очищен - - - - - - - Copied - Скопировано - - - - Save AmneziaVPN config - Сохранить конфиг AmneziaVPN - - - - - Generating... - Генерация... - - - - Error while generating connection profile - Ошибка во время генерации профиля - - - - Save OpenVPN config - Сохранить OpenVPN конфиг - - - + VPN Protocols is not installed. Please install VPN container at first - VPN протоколы ещё не установлены. Установите VPN контейнеры + - - VPN Protocol not chosen - VPN протокол не выбран + + Connection... + - - Software version - Версия программы + + Disconnect + - - Protocol: - Протокол: + + Reconnection... + - - Press Generate config - Нажмите Сгенерировать конфиг + + + + + Connect + - - Share server full access - Расшарить полный доступ + + Disconnection... + + + + + ContextMenu + + + C&ut + + + + + &Copy + + + + + &Paste + + + + + &SelectAll + + + + + ExportController + + + Save AmneziaVPN config + + + + + NotificationHandler + + + + AmneziaVPN + + + + + VPN Connected + + + + + VPN Disconnected + + + + + AmneziaVPN notification + + + + + Unsecured network detected: + + + + + OpenVpnSettings + + + VPN Addresses Subnet + + + + + Network protocol + + + + + Port + + + + + Auto-negotiate encryption + + + + + + Hash + + + + + SHA512 + + + + + SHA384 + + + + + SHA256 + + + + + SHA3-512 + + + + + SHA3-384 + + + + + SHA3-256 + + + + + whirlpool + + + + + BLAKE2b512 + + + + + BLAKE2s256 + + + + + SHA1 + + + + + + Cipher + + + + + AES-256-GCM + + + + + AES-192-GCM + + + + + AES-128-GCM + + + + + AES-256-CBC + + + + + AES-192-CBC + + + + + AES-128-CBC + + + + + ChaCha20-Poly1305 + + + + + ARIA-256-CBC + + + + + CAMELLIA-256-CBC + + + + + none + + + + + TLS auth + + + + + Block DNS requests outside of VPN + + + + + Additional configuration commands + + + + + PageAbout + + + About Amnezia + + + + + AmneziaVPN is opensource software, it's free forever. Our goal is to make the best VPN client in the world. +<ul> +<li>Sources on <a href="https://github.com/amnezia-vpn/desktop-client">GitHub</a></li> +<li><a href="https://amnezia.org/">Web Site</a></li> +<li><a href="https://t.me/amnezia_vpn_en">Telegram group</a></li> +<li><a href="https://signal.group/#CjQKIB2gUf8QH_IXnOJMGQWMDjYz9cNfmRQipGWLFiIgc4MwEhAKBONrSiWHvoUFbbD0xwdh">Signal group</a></li> +</ul> + + + + + + Support + + + + + Have questions? You can get support by: +<ul> +<li><a href="https://t.me/amnezia_vpn_en">Telegram group</a> (preferred way)</li> +<li>Create issue on <a href="https://github.com/amnezia-vpn/desktop-client/issues">GitHub</a></li> +<li>Email to: <a href="support@amnezia.org">support@amnezia.org</a></li> +</ul> + + + + + Donate + + + + + Please support Amnezia project by donation, we really need it now more than ever. +<ul> +<li>By credit card on <a href="https://www.patreon.com/amneziavpn">Patreon</a> (starting from $1)</li> +<li>Send some coins to addresses listed <a href="https://github.com/amnezia-vpn/desktop-client/blob/master/README.md">on GitHub page</a></li> +</ul> + + + + + + PageAdvancedServerSettings + + + Advanced server settings + + + + + Clients Management + + + + + PageAppSetting + + + Application Settings + + + + + Auto connect + + + + + Auto start + + + + + Start minimized + + + + + Check for updates + + + + + Keep logs + + + + + Open logs folder + + + + + Export logs + + + + + Clear logs + + + + + Cleared + + + + + Backup and restore configuration + + + + + Backup app config + + + + + Restore app config + + + + + PageClientInfoOpenVPN + + + Client Info + + + + + Client name + + + + + Certificate id + + + + + Certificate + + + + + Revoke Certificate + + + + + PageClientInfoWireGuard + + + Client Info + + + + + Client name + + + + + Public Key + + + + + Revoke Key + + + + + PageClientManagement + + + Clients Management + + + + + PageDeinstalling + + + Removing services from + + + + + PageGeneralSettings + + + App settings + + + + + Network settings + + + + + Server Settings + + + + + Share connection + + + + + Servers + + + + + Add server + + + + + Exit + + + + + PageHome + + + Протокол подключения + + + + + Servers + + + + + PageNetworkSetting + + + DNS Servers + + + + + Use AmneziaDNS service (recommended) + + + + + Use AmneziaDNS container on your server, when it installed. + +Your AmneziaDNS server available only when it installed and VPN connected, it has internal IP address 172.29.172.254 + +If AmneziaDNS service is not installed on the same server, or this option is unchecked, the following DNS servers will be used: + + + + + Primary DNS server + + + + + + Reset to default + + + + + Secondary DNS server + + + + + PageNewServer + + + Setup your server to use VPN + + + + + If you want easily configure your server just run Wizard + + + + + Run Setup Wizard + + + + + Press configure manually to choose VPN protocols you want to install + + + + + Configure + + + + + PageNewServerProtocols + + + Select VPN protocols + + + + + Setup server + + + + + Select protocol container + + + + + Port + + + + + Network Protocol + + + + + udp + + + + + tcp + + + + + PageProtoCloak + + + Cloak Settings + + + + + Cipher + + + + + chacha20-poly1305 + + + + + aes-256-gcm + + + + + aes-192-gcm + + + + + aes-128-gcm + + + + + Fake Web Site + + + + + Port + + + + + Save and restart VPN + + + + + Cancel + + + + + PageProtoOpenVPN + + + OpenVPN Settings + + + + + VPN Addresses Subnet + + + + + Network protocol + + + + + TCP + + + + + UDP + + + + + Port + + + + + Auto-negotiate encryption + + + + + Cipher + + + + + AES-256-GCM + + + + + AES-192-GCM + + + + + AES-128-GCM + + + + + AES-256-CBC + + + + + AES-192-CBC + + + + + AES-128-CBC + + + + + ChaCha20-Poly1305 + + + + + ARIA-256-CBC + + + + + CAMELLIA-256-CBC + + + + + none + + + + + Hash + + + + + SHA512 + + + + + SHA384 + + + + + SHA256 + + + + + SHA3-512 + + + + + SHA3-384 + + + + + SHA3-256 + + + + + whirlpool + + + + + BLAKE2b512 + + + + + BLAKE2s256 + + + + + SHA1 + + + + + Enable TLS auth + + + + + Block DNS requests outside of VPN + + + + + Additional client config commands → + + + + + Additional server config commands → + + + + + Save and restart VPN + + + + + Cancel + + + + + PageProtoSftp + + + SFTP settings + + + + + Port + + + + + User Name + + + + + Password + + + + + Restore drive when client starts + + + + + Mount drive + + + + + PageProtoShadowSocks + + + ShadowSocks Settings + + + + + Cipher + + + + + chacha20-ietf-poly1305 + + + + + xchacha20-ietf-poly1305 + + + + + aes-256-gcm + + + + + aes-192-gcm + + + + + aes-128-gcm + + + + + Port + + + + + Save and restart VPN + + + + + Cancel + + + + + PageProtoTorWebSite + + + Tor Web Site settings + + + + + Web site onion address + + + + + Notes:<ul> +<li>Use <a href="https://www.torproject.org/download/">Tor Browser</a> to open this url.</li> +<li>After installation it takes several minutes while your onion site will become available in the Tor Network.</li> +<li>When configuring WordPress set the domain as this onion address.</li> +</ul> + + + + + + PageProtoWireGuard + + + WireGuard Settings + + + + + PageQrDecoderIos + + + Import configuration + + + + + PageServerConfiguringProgress + + + Configuring... + + + + + Please wait. + + + + + Cancel + + + + + PageServerContainers + + + + Install new service + + + + + Installed services + + + + + Default + + + + + Port + + + + + Network Protocol + + + + + udp + + + + + tcp + + + + + Cancel + + + + + Continue + Продолжить + + + + Installed Protocols and Services + + + + + Remove container + + + + + This action will erase all data of this container on the server. + + + + + PageServerList + + + Servers + + + + + PageServerSettings + + + Server settings + + + + + Protocols and Services + + + + + Share Server (FULL ACCESS) + + + + + Advanced server settings + + + + + Forget this server + + + + + PageSettings + + + Settings + + + + + Servers + + + + + Connection + + + + + Application + + + + + Backup + + + + + About AmneziaVPN + + + + + PageSettingsAbout + + + Support the project with a donation + + + + + This is a free and open source application. If you like it, support the developers with a donation. +And if you don't like the app, all the more support it - the donation will be used to improve the app. + + + + + Card on Patreon + + + + + Show other methods on Github + + + + + Contacts + + + + + Telegram group + + + + + To discuss features + + + + + Mail + + + + + For reviews and bug reports + + + + + Github + + + + + Website + + + + + Check for updates + + + + + PageSettingsApplication + + + Application + + + + + Language + + + + + Reset settings and remove all data from the application + + + + + PageSettingsBackup + + + Backup + + + + + Save logs + + + + + Open folder with logs + + + + + Save logs to file + + + + + Clear logs + + + + + Configuration backup + + + + + It will help you instantly restore connection settings at the next installation + + + + + Make a backup + + + + + Restore from backup + + + + + PageSettingsConnection + + + Connection + + + + + Use AmnesiaDNS if installed on the server + + + + + Internal IP address 172.29.172.254 + + + + + DNS servers + + + + + If AmneziaDNS is not used or installed + + + + + Split site tunneling + + + + + Allows you to connect to some sites through a secure connection, and to others bypassing it + + + + + Separate application tunneling + + + + + Allows you to use the VPN only for certain applications + + + + + PageSettingsDns + + + DNS servers + + + + + If AmneziaDNS is not used or installed + + + + + Save + + + + + PageSettingsServerData + + + All installed containers have been added to the application + + + + + Не найдено установленных контейнеров + + + + + Clear Amnezia cache + + + + + May be needed when changing other settings + + + + + Clear cached profiles? + Очистить закешированные профили + + + + some description + + + + + + + Continue + Продолжить + + + + + + Cancel + + + + + Проверить сервер на наличие ранее установленных сервисов Amnezia + + + + + Добавим их в приложение, если они не отображались + + + + + Remove server from application + + + + + Remove server? + + + + + All installed AmneziaVPN services will still remain on the server. + + + + + Clear server from Amnezia software + + + + + Clear server from Amnezia software? + + + + + All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted. + + + + + PageSettingsServerInfo + + + Server name + + + + + Save + + + + + Protocols + + + + + Services + + + + + Data + + + + + PageSetupWizard + + + Setup your server to use VPN + + + + + High censorship level + + + + + I'm living in a country with a high censorship level. Many of the foreign websites and VPNs are blocked by my government. I want to setup a reliable VPN, which can not be detected by my internet provider and my government. +OpenVPN and ShadowSocks over Cloak (VPN obfuscation) profiles will be installed. + + + + + + Medium censorship level + + + + + I'm living in a country with a medium censorship level. Some websites are blocked by my government, but VPNs are not blocked at all. I want to setup a flexible solution. +OpenVPN over ShadowSocks profile will be installed. + + + + + + Low censorship level + + + + + I want to improve my privacy on the internet. +OpenVPN profile will be installed. + + + + + + Next + + + + + PageSetupWizardCredentials + + + Server connection + + + + + Server IP address [:port] + + + + + + Enter the address in the format 255.255.255.255:88 + + + + + Login to connect via SSH + + + + + Password / Private key + + + + + Set up a server the easy way + + + + + Select protocol to install + + + + + Ip address cannot be empty + + + + + Login cannot be empty + + + + + Password/private key cannot be empty + + + + + PageSetupWizardEasy + + + What is the level of internet control in your region? + + + + + Continue + Продолжить + + + + PageSetupWizardHighLevel + + + Setup Wizard + + + + + AmneziaVPN will install a VPN protocol which is not visible to your internet provider and government firewall. Your VPN connection will be seen by your internet provider as regular web traffic to a particular website. + +You SHOULD set this website address to some foreign website which is not blocked by your internet provider. In other words, you need to type some foreign website address which is accessible to you without a VPN. + + + + + Type another web site address for masking or keep it by default. Your internet provider will think you working on this web site when you connected to VPN. + + + + + OpenVPN and ShadowSocks over Cloak (VPN obfuscation) profiles will be installed. + +This protocol support exporting connection profiles to mobile devices by exporting ShadowSocks and Cloak configs (you should launch the 3rd party open source VPN client - ShadowSocks VPN and install Cloak plugin). + + + + + Next + + + + + PageSetupWizardInstalling + + + + The container you are trying to install is already installed on the server. All installed containers have been added to the application + + + + + The server has already been added to the application + + + + + PageSetupWizardLowLevel + + + Setup Wizard + + + + + AmneziaVPN will install the OpenVPN protocol with public/private key pairs generated on both server and client sides. + +You can also configure the connection on your mobile device by copying the exported ".ovpn" file to your device, and setting up the official OpenVPN client. + +We recommend not to use messaging applications for sending the connection profile - it contains VPN private keys. + + + + + OpenVPN profile will be installed + + + + + Start configuring + + + + + PageSetupWizardMediumLevel + + + Setup Wizard + + + + + AmneziaVPN will install a VPN protocol which is difficult to detect by your internet provider and government firewall (but possible). In most cases, this is the most suitable protocol. This protocol is faster compared to the VPN protocols with "VPN masking". + +This protocol supports exporting connection profiles to mobile devices by using QR codes (you should launch the 3rd party open source VPN client - ShadowSocks VPN). + + + + + OpenVPN over ShadowSocks profile will be installed + + + + + Next + + + + + PageSetupWizardProtocolSettings + + + Installing + + + + + protocol description + + + + + More detailed + + + + + detailed protocol description + + + + + Close + + + + + Установить + + + + + PageSetupWizardStart + + + У меня есть данные для подключения + + + + + У меня ничего нет + + + + + PageSetupWizardTextKey + + + Connection key + + + + + A line that starts with vpn://... + + + + + Key + + + + + Insert + + + + + Continue + Продолжить + + + + PageSetupWizardVPNMode + + + Setup Wizard + + + + + Optional. + +You can enable VPN mode "For selected sites" and add blocked sites you need to visit manually. If you will choose this option, you will need add every blocked site you want to visit to the access list. You may switch between modes later. + +Please note, you should add addresses to the list after VPN connection established. You may add any domain, URL or IP address, it will be resolved to IP address. + + + + + Turn on mode "VPN for selected sites" + + + + + Start configuring + + + + + PageSetupWizardViewConfig + + + New connection + + + + + Do not use connection code from public sources. It could be created to intercept your data. + + + + + Collapse content + + + + + Show content + + + + + Connect + + + + + PageShare + + + For the AmnesiaVPN app + + + + + OpenVpn native format + + + + + WireGuard native format + + + + + VPN Access + + + + + Connection + + + + + Full + + + + + VPN access without the ability to manage the server + + + + + Full access to server + + + + + Server and service + + + + + Server + + + + + Protocols and services + + + + + + Connection to + + + + + + File with connection settings to + + + + + + Connection format + + + + + Share + + + + + PageShareConnection + + + Share protocol config + + + + + Share for Amnezia + + + + + Share for + + + + + PageShareProtoAmnezia + + + Share for Amnezia + + + + + Anyone who logs in with this code will have the same permissions to use VPN and YOUR SERVER as you. + +This code includes your server credentials! + +Provide this code only to TRUSTED users. + + + + + Anyone who logs in with this code will be able to connect to this VPN server. + +This code does not include server credentials. + +New encryption keys pair will be generated. + + + + + Share + + + + + Save to file + + + + + Save AmneziaVPN config + + + + + Scan QR code using AmneziaVPN mobile + + + + + PageShareProtoCloak + + + Share Cloak Settings + + + + + Note: Cloak protocol using same password for all connections + + + + + Share + + + + + Save to file + + + + + Save AmneziaVPN config + + + + + PageShareProtoIkev2 + + + Share IKEv2 Settings + + + + + + Export p12 certificate + + + + + + Export config for Apple + + + + + + Export config for StrongSwan + + + + + PageShareProtoOpenVPN + + + Share OpenVPN Settings + + + + + New encryption keys pair will be generated. + + + + + Share + + + + + Save to file + + + + + Save OpenVPN config + + + + + PageShareProtoSftp + + + Share SFTP settings + + + + + PageShareProtoShadowSocks + + + Share ShadowSocks Settings + + + + + Note: ShadowSocks protocol using same password for all connections + + + + + Copy config + + + + + Connection string + + + + + Copy string + + + + + PageShareProtoTorWebSite + + + Share Tor Web site + + + + + PageShareProtoWireGuard + + + Share WireGuard Settings + + + + + New encryption keys pair will be generated. + + + + + Share + + + + + Save to file + + + + + Save OpenVPN config + + + + + PageShareProtocolBase + + + Generate config + + + + + Generating config... + + + + + Show config + + + + + PageSites + + + Web site/Hostname/IP address/Subnet + + + + + yousite.com or IP address + + + + + Import IP addresses + + + + + Delete selected + + + + + Select all + + + + + Export all + + + + + PageStart + + + Setup your server to use VPN + + + + + Connect to the already created VPN server + + + + + + Set up your own server + + + + + Import connection + + + + + Connection code + + + + + Connect + + + + + Open file + + + + + Scan QR code + + + + + Restore app config + + + + + How to get own server? → + + + + + Server IP address [:port] + + + + + Login to connect via SSH + + + + + + Password + + + + + + Connect using SSH key + + + + + Private key + + + + + Connect using SSH password + + + + + PageTest + + + Протоколы + + + + + Сервисы + + + + + Данные + + + + + + Forget this server + + + + + SHA512 + + + + + SHA384 + + + + + SHA256 + + + + + SHA3-512 + + + + + SHA3-384 + + + + + SHA3-256 + + + + + whirlpool + + + + + BLAKE2b512 + + + + + BLAKE2s256 + + + + + SHA1 + + + + + + + Auto-negotiate encryption + + + + + PageVPN + + + Donate + + + + + Server + + + + + Profile + + + + + Proto + + + + + DNS + + + + + How to use VPN + + + + + For all connections + + + + + Except selected sites + + + + + For selected sites + + + + + + Add site + + + + + PageViewConfig + + + Check config + + + + + Attention! +The config above contains cached OpenVPN connection profile. +AmneziaVPN detected this profile may contain malicious scripts. Please, carefully review the config and import this config only if you completely trust it. + + + + + Suspicious string: + + + + + Cached connection profile: + + + + + Cancel + + + + + Import config + + + + + PopupType + + + Close + QObject - - AmneziaVPN is already running. - Приложение AmneziaVPN уже запущено. - No error @@ -1166,656 +2502,636 @@ This code does not include server credentials. Server port already used. Check for another software + + + Server error: Docker container missing + + + + + Server error: Docker failed + + - Ssh connection error + Installation canceled by user - Ssh connection timeout - - - - - Ssh protocol error - - - - - Ssh server ket check failed + The user does not have permission to use sudo - Ssh key file error + Ssh request was denied - Ssh authentication error + Ssh request was interrupted - Ssh session closed - - - - Ssh internal error - - Failed to create remote process on server + + Invalid private key or invalid passphrase entered - - Failed to start remote process on server + + The selected private key format is not supported, use openssh ED25519 key types or PEM key types + + + + + Timeout connecting to server - Remote process on server crashed + Sftp error: End-of-file encountered + + + + + Sftp error: File does not exist + + + + + Sftp error: Permission denied - Failed to save config to disk + Sftp error: Generic failure - OpenVPN config missing + Sftp error: Garbage received from server - OpenVPN management server error + Sftp error: No connection has been set up - EasyRSA runtime error + Sftp error: There was a connection, but we lost it + + + + + Sftp error: Operation not supported by libssh yet + + + + + Sftp error: Invalid file handle - OpenVPN executable missing + Sftp error: No such file or directory path exists - EasyRsa executable missing + Sftp error: An attempt to create an already existing file or directory has been made - Amnezia helper service error - Ошибка локального сервиса Amnezia + Sftp error: Write-protected filesystem + - + + Sftp error: No media was in remote drive + + + + + Failed to save config to disk + + + + + OpenVPN config missing + + + + + OpenVPN management server error + + + + + OpenVPN executable missing + + + + + ShadowSocks (ss-local) executable missing + + + + + Cloak (ck-client) executable missing + + + + + Amnezia helper service error + + + + + OpenSSL failed + + + + Can't connect: another VPN connection is active - + + Can't setup OpenVPN TAP network adapter + + + + + VPN pool error: no available addresses + + + + + The config does not contain any containers and credentiaks for connecting to the server + + + + Internal error - - - QSsh::Internal::SftpChannelPrivate - - Server could not start SFTP subsystem. + + IPsec - - The SFTP server finished unexpectedly with exit code %1. + + + Web site in Tor network - - The SFTP server crashed: %1. + + + DNS Service - - Unexpected packet of type %1. + + Sftp file sharing service - - Protocol version mismatch: Expected %1, got %2 + + OpenVPN container - - Unknown error. + + Container with OpenVpn and ShadowSocks - - Created remote directory "%1". + + Container with OpenVpn and ShadowSocks protocols configured with traffic masking by Cloak plugin - - Remote directory "%1" already exists. + + WireGuard container - - Error creating directory "%1": %2 + + IPsec container - - Could not open local file "%1": %2 + + Sftp file sharing service - is secure FTP service - - Remote directory could not be opened for reading. + + Sftp service - - Failed to list remote directory contents. - - - - - Failed to close remote directory. - - - - - Failed to open remote file for reading. - - - - - Failed to retrieve information on the remote file ('stat' failed). - - - - - Failed to read remote file. - - - - - - Failed to close remote file. - - - - - Failed to open remote file for writing. - - - - - Failed to write remote file. - - - - - Cannot append to remote file: Server does not support the file size attribute. - - - - - SFTP channel closed unexpectedly. - - - - - Server could not start session: %1 - - - - - Error reading local file: %1 + + An error occurred while saving the list of clients. - QSsh::Internal::SshChannelManager + SelectContainer - - Unexpected request success packet. + + VPN containers - - Unexpected request failure packet. - - - - - Invalid channel id %1 + + Other containers - QSsh::Internal::SshConnectionPrivate + SelectLanguageDrawer - - SSH Protocol error: %1 - - - - - Botan library exception: %1 - - - - - Server identification string is %n characters long, but the maximum allowed length is 255. - - - - - - - - - Server identification string contains illegal NUL character. - - - - - Server Identification string "%1" is invalid. - - - - - Server protocol version is "%1", but needs to be 2.0 or 1.99. - - - - - Server identification string is invalid (missing carriage return). - - - - - Server reports protocol version 1.99, but sends data before the identification string, which is not allowed. - - - - - - - - Unexpected packet of type %1. - - - - - Password expired. - - - - - Server rejected key. - - - - - Server rejected password. - - - - - The server sent an unexpected SSH packet of type SSH_MSG_UNIMPLEMENTED. - - - - - Server closed connection: %1 - - - - - Connection closed unexpectedly. - - - - - Timeout waiting for reply from server. - - - - - No private key file given. + + Choose language - QSsh::Internal::SshRemoteProcessPrivate + ServerConfiguringProgressLogic - - Process killed by signal + + + Please wait, configuring process may take up to 5 minutes - - Server sent invalid signal "%1" + + Configuring... + + + + + Operation finished - QSsh::SftpFileSystemModel + ServerContainersLogic - - File Type + + Error occurred while configuring server. - - File Name + + Error message: - - Error getting "stat" info about "%1": %2 - - - - - Error listing contents of directory "%1": %2 + + See logs for details. - QSsh::Ssh + ServerSettingsLogic - - Failed to open key file "%1" for reading: %2 + + + Clear client cached profile - - Failed to open key file "%1" for writing: %2 + + Service: - - Password Required + + Cache cleared - - - Please enter the password for your private key. - - - - - QSsh::SshKeyCreationDialog - - - SSH Key Configuration - - - - - Options - - - - - Key algorithm: - - - - - &RSA - - - - - &DSA - - - - - ECDSA - - - - - Key &size: - - - - - Private key file: - - - - - - Browse... - - - - - Public key file: - - - - - &Generate And Save Key Pair - - - - - &Cancel - - - - - Choose... - - - - - Key Generation Failed - - - - - Choose Private Key File Name - - - - - Cannot Save Key File - - - - - Failed to create directory: "%1". - - - - - Cannot Save Private Key File - - - - - The private key file could not be saved: %1 - - - - - Cannot Save Public Key File - - - - - The public key file could not be saved: %1 - - - - - File Exists - - - - - There already is a file of that name. Do you want to overwrite it? - - - - - ServerWidget - - - Form - - - - - Description - - - - - Address - - - - - Set as default - - - - - Share connection - Поделиться подключением - - - - Connection - - - - - Server settings - Настройки сервера - Settings - + Server #1 - - + + Server - SshConnection + SettingsController - - Server and client capabilities don't match. Client list was: %1. -Server list was %2. + + Software version + + + + + Save log + + + + + Backup application config + + + + + Open backup - SshKeyGenerator + ShareConnectionButtonCopyType - - Error generating key: %1 + + Copy - - Password for Private Key + + Copied + + + + + ShareConnectionDrawer + + + Save connection code - - It is recommended that you secure your private key -with a password, which you can enter below. + + Copy - - Encrypt Key File + + Show content - - Do Not Encrypt Key File + + To read the QR code in the Amnezia app, select "Add Server" → "I have connection details" + + + + + ShareConnectionLogic + + + Error while generating connection profile + + + + + Error occurred while generating the config. + + + + + Error message: + + + + + See logs for details. + + + + + SitesLogic + + + These sites will be opened using VPN + + + + + These sites will be excepted from VPN + + + + + StartPageLogic + + + + Connect + + + + + + Please fill in all fields + + + + + Connecting... + + + + + Open config file + + + + + SystemTrayNotificationHandler + + + Show + + + + + Connect + + + + + Disconnect + + + + + Visit Website + + + + + Quit + + + + + UiLogic + + + Error occurred while configuring server. + + + + + Error message: + + + + + See logs for details. VpnConnection - + Mbps + + VpnLogic + + + + 0 Mbps + + + + + AmneziaVPN not supporting selected protocol on this device. Select another protocol. + + + + + VPN Protocols is not installed. + Please install VPN container at first + + + + + VPN Protocol not chosen + + + VpnProtocol - + Unknown - Неизвестно + - + Disconnected - Отключено + - + Preparing - Подготовка + - + Connecting... - Подключение... + - + Connected - Подключено + - + Disconnecting... - Отключение... + - + Reconnecting... - Переподключение... + - + Error - Ошибка + + + + + amnezia::ContainerProps + + + Low + + + + + High + + + + + Medium + + + + + Many foreign websites and VPN providers are blocked + + + + + Some foreign sites are blocked, but VPN providers are not blocked + + + + + I just want to increase the level of privacy + + + + + main + + + It's public key. Private key required + + + + + Ssh log + + + + + App log + + + + + Wrap words + diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index fcfcc7af..42dd2231 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -2,9 +2,9 @@ #include -#include "defines.h" #include "logger.h" #include "utilities.h" +#include "version.h" SettingsController::SettingsController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, diff --git a/client/ui/models/languageModel.cpp b/client/ui/models/languageModel.cpp new file mode 100644 index 00000000..e95b2ccf --- /dev/null +++ b/client/ui/models/languageModel.cpp @@ -0,0 +1,62 @@ +#include "languageModel.h" + +LanguageModel::LanguageModel(std::shared_ptr settings, QObject *parent) + : m_settings(settings), QAbstractListModel(parent) +{ + QMetaEnum metaEnum = QMetaEnum::fromType(); + for (int i = 0; i < metaEnum.keyCount(); i++) { + m_availableLanguages.push_back( + LanguageModelData { metaEnum.valueToKey(i), static_cast(i) }); + } +} + +int LanguageModel::rowCount(const QModelIndex &parent) const +{ + return static_cast(m_availableLanguages.size()); +} + +QVariant LanguageModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(m_availableLanguages.size())) { + return QVariant(); + } + + switch (role) { + case NameRole: { + return m_availableLanguages[index.row()].name; + break; + } + case IndexRole: { + return static_cast(m_availableLanguages[index.row()].index); + break; + } + } + return QVariant(); +} + +QHash LanguageModel::roleNames() const +{ + QHash roles; + roles[NameRole] = "languageName"; + roles[IndexRole] = "languageIndex"; + return roles; +} + +void LanguageModel::changeLanguage(const LanguageSettings::AvailableLanguageEnum language) +{ + switch (language) { + case LanguageSettings::AvailableLanguageEnum::English: emit updateTranslations(QLocale::English); break; + case LanguageSettings::AvailableLanguageEnum::Russian: emit updateTranslations(QLocale::Russian); break; + default: emit updateTranslations(QLocale::English); break; + } +} + +int LanguageModel::getCurrentLanguageIndex() +{ + auto locale = m_settings->getAppLanguage(); + switch (locale.language()) { + case QLocale::English: return static_cast(LanguageSettings::AvailableLanguageEnum::English); break; + case QLocale::Russian: return static_cast(LanguageSettings::AvailableLanguageEnum::Russian); break; + default: return static_cast(LanguageSettings::AvailableLanguageEnum::English); break; + } +} diff --git a/client/ui/models/languageModel.h b/client/ui/models/languageModel.h new file mode 100644 index 00000000..b3ff4f6e --- /dev/null +++ b/client/ui/models/languageModel.h @@ -0,0 +1,62 @@ +#ifndef LANGUAGEMODEL_H +#define LANGUAGEMODEL_H + +#include +#include + +#include "settings.h" + +namespace LanguageSettings +{ + Q_NAMESPACE + enum class AvailableLanguageEnum { + English, + Russian + }; + Q_ENUM_NS(AvailableLanguageEnum) + + static void declareQmlAvailableLanguageEnum() + { + qmlRegisterUncreatableMetaObject(LanguageSettings::staticMetaObject, "AvailableLanguageEnum", 1, 0, + "AvailableLanguageEnum", QString()); + } +} + +struct LanguageModelData +{ + QString name; + LanguageSettings::AvailableLanguageEnum index; +}; + +class LanguageModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + NameRole = Qt::UserRole + 1, + IndexRole + }; + + LanguageModel(std::shared_ptr settings, QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +public slots: + void changeLanguage(const LanguageSettings::AvailableLanguageEnum language); + int getCurrentLanguageIndex(); + +signals: + void updateTranslations(const QLocale &locale); + +protected: + QHash roleNames() const override; + +private: + QVector m_availableLanguages; + + std::shared_ptr m_settings; +}; + +#endif // LANGUAGEMODEL_H diff --git a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml index 51bffc03..71299b1f 100644 --- a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml +++ b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml @@ -18,12 +18,14 @@ DrawerType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right + spacing: 0 Header2TextType { Layout.fillWidth: true Layout.topMargin: 24 Layout.rightMargin: 16 Layout.leftMargin: 16 + Layout.bottomMargin: 32 Layout.alignment: Qt.AlignHCenter text: "Данные для подключения" diff --git a/client/ui/qml/Components/SelectLanguageDrawer.qml b/client/ui/qml/Components/SelectLanguageDrawer.qml new file mode 100644 index 00000000..f1ffa416 --- /dev/null +++ b/client/ui/qml/Components/SelectLanguageDrawer.qml @@ -0,0 +1,137 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import "../Controls2" +import "../Controls2/TextTypes" + +DrawerType { + id: root + + width: parent.width + height: parent.height * 0.9 + + ColumnLayout { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 + + BackButtonType { + backButtonImage: "qrc:/images/controls/arrow-left.svg" + backButtonFunction: function() { + root.close() + } + } + } + + FlickableType { + anchors.top: backButton.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + contentHeight: content.implicitHeight + + ColumnLayout { + id: content + + anchors.fill: parent + + Header2Type { + id: header + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + headerText: qsTr("Choose language") + } + + ListView { + id: listView + + Layout.fillWidth: true + height: listView.contentItem.height + + clip: true + interactive: false + + model: LanguageModel + currentIndex: LanguageModel.getCurrentLanguageIndex() + + ButtonGroup { + id: buttonGroup + } + + delegate: Item { + implicitWidth: root.width + implicitHeight: content.implicitHeight + + ColumnLayout { + id: delegateContent + + anchors.fill: parent + + 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 + + spacing: 0 + + z: 1 + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 20 + Layout.bottomMargin: 20 + + text: languageName + } + + Image { + source: "qrc:/images/controls/check.svg" + visible: radioButton.checked + + width: 24 + height: 24 + + Layout.rightMargin: 8 + } + } + + ButtonGroup.group: buttonGroup + checked: listView.currentIndex === index + + onClicked: { + listView.currentIndex = index + LanguageModel.changeLanguage(languageIndex) + } + } + } + } + } + } + } +} diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 720e3206..99e172cc 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -105,8 +105,6 @@ DrawerType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 anchors.topMargin: 16 backButtonFunction: function() { diff --git a/client/ui/qml/Components/ShowDetailsDrawer.qml b/client/ui/qml/Components/ShowDetailsDrawer.qml deleted file mode 100644 index 2f6d2656..00000000 --- a/client/ui/qml/Components/ShowDetailsDrawer.qml +++ /dev/null @@ -1,10 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -import "../Controls2" -import "../Controls2/TextTypes" - -Item { - -} diff --git a/client/ui/qml/Controls2/BackButtonType.qml b/client/ui/qml/Controls2/BackButtonType.qml index 0ccb7345..91f5f28f 100644 --- a/client/ui/qml/Controls2/BackButtonType.qml +++ b/client/ui/qml/Controls2/BackButtonType.qml @@ -17,6 +17,7 @@ Item { id: content anchors.fill: parent + anchors.leftMargin: 8 ImageButtonType { image: backButtonImage diff --git a/client/ui/qml/Controls2/BasicButtonType.qml b/client/ui/qml/Controls2/BasicButtonType.qml index 05074fa9..d9168466 100644 --- a/client/ui/qml/Controls2/BasicButtonType.qml +++ b/client/ui/qml/Controls2/BasicButtonType.qml @@ -1,6 +1,8 @@ import QtQuick import QtQuick.Controls +import "TextTypes" + Button { id: root @@ -46,12 +48,8 @@ Button { cursorShape: Qt.PointingHandCursor } - contentItem: Text { + contentItem: ButtonTextType { anchors.fill: background - font.family: "PT Root UI VF" - font.styleName: "normal" - font.weight: 400 - font.pixelSize: 16 color: textColor text: root.text horizontalAlignment: Text.AlignHCenter diff --git a/client/ui/qml/Controls2/DividerType.qml b/client/ui/qml/Controls2/DividerType.qml index 6341807a..bf01e7a1 100644 --- a/client/ui/qml/Controls2/DividerType.qml +++ b/client/ui/qml/Controls2/DividerType.qml @@ -3,6 +3,10 @@ import QtQuick.Layouts Rectangle { Layout.fillWidth: true + + Layout.leftMargin: 16 + Layout.rightMargin: 16 + height: 1 color: "#2C2D30" } diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 9b9c718a..85989ae6 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -146,8 +146,6 @@ Item { anchors.left: parent.left anchors.right: parent.right anchors.topMargin: 16 - anchors.leftMargin: 16 - anchors.rightMargin: 16 BackButtonType { backButtonImage: root.headerBackButtonImage diff --git a/client/ui/qml/Controls2/TextTypes/ButtonTextType.qml b/client/ui/qml/Controls2/TextTypes/ButtonTextType.qml index d26594d6..e3b14e63 100644 --- a/client/ui/qml/Controls2/TextTypes/ButtonTextType.qml +++ b/client/ui/qml/Controls2/TextTypes/ButtonTextType.qml @@ -1,11 +1,12 @@ import QtQuick Text { - height: 24 + lineHeight: 24 + lineHeightMode: Text.FixedHeight color: "#D7D8DB" font.pixelSize: 16 - font.weight: Font.Medium + font.weight: 500 font.family: "PT Root UI VF" wrapMode: Text.WordWrap diff --git a/client/ui/qml/Controls2/TextTypes/CaptionTextType.qml b/client/ui/qml/Controls2/TextTypes/CaptionTextType.qml index 15cc96c1..b9e41da2 100644 --- a/client/ui/qml/Controls2/TextTypes/CaptionTextType.qml +++ b/client/ui/qml/Controls2/TextTypes/CaptionTextType.qml @@ -1,11 +1,12 @@ import QtQuick Text { - height: 16 + lineHeight: 16 + lineHeightMode: Text.FixedHeight color: "#0E0E11" font.pixelSize: 13 - font.weight: Font.Normal + font.weight: 400 font.family: "PT Root UI VF" font.letterSpacing: 0.02 diff --git a/client/ui/qml/Controls2/TextTypes/Header1TextType.qml b/client/ui/qml/Controls2/TextTypes/Header1TextType.qml index 99addc7b..86b61d7a 100644 --- a/client/ui/qml/Controls2/TextTypes/Header1TextType.qml +++ b/client/ui/qml/Controls2/TextTypes/Header1TextType.qml @@ -1,13 +1,14 @@ import QtQuick Text { - height: 38 + lineHeight: 38 + lineHeightMode: Text.FixedHeight color: "#D7D8DB" font.pixelSize: 36 - font.weight: Font.Bold + font.weight: 700 font.family: "PT Root UI VF" - font.letterSpacing: -0.03 + font.letterSpacing: -1.08 wrapMode: Text.WordWrap } diff --git a/client/ui/qml/Controls2/TextTypes/Header2TextType.qml b/client/ui/qml/Controls2/TextTypes/Header2TextType.qml index ed96f6f1..9e2a8ae9 100644 --- a/client/ui/qml/Controls2/TextTypes/Header2TextType.qml +++ b/client/ui/qml/Controls2/TextTypes/Header2TextType.qml @@ -1,11 +1,12 @@ import QtQuick Text { - height: 30 + lineHeight: 30 + lineHeightMode: Text.FixedHeight color: "#D7D8DB" font.pixelSize: 25 - font.weight: Font.Bold + font.weight: 700 font.family: "PT Root UI VF" wrapMode: Text.WordWrap diff --git a/client/ui/qml/Controls2/TextTypes/LabelTextType.qml b/client/ui/qml/Controls2/TextTypes/LabelTextType.qml index a2ffa18b..d47d460d 100644 --- a/client/ui/qml/Controls2/TextTypes/LabelTextType.qml +++ b/client/ui/qml/Controls2/TextTypes/LabelTextType.qml @@ -1,11 +1,12 @@ import QtQuick Text { - height: 16 + lineHeight: 16 + lineHeightMode: Text.FixedHeight color: "#878B91" font.pixelSize: 13 - font.weight: Font.Normal + font.weight: 400 font.family: "PT Root UI VF" font.letterSpacing: 0.02 diff --git a/client/ui/qml/Controls2/TextTypes/ListItemTitleType.qml b/client/ui/qml/Controls2/TextTypes/ListItemTitleType.qml index 23069db2..30bd7900 100644 --- a/client/ui/qml/Controls2/TextTypes/ListItemTitleType.qml +++ b/client/ui/qml/Controls2/TextTypes/ListItemTitleType.qml @@ -1,11 +1,12 @@ import QtQuick Text { - height: 21.6 + lineHeight: 21.6 + lineHeightMode: Text.FixedHeight color: "#D7D8DB" font.pixelSize: 18 - font.weight: Font.Normal + font.weight: 400 font.family: "PT Root UI VF" wrapMode: Text.WordWrap diff --git a/client/ui/qml/Controls2/TextTypes/ParagraphTextType.qml b/client/ui/qml/Controls2/TextTypes/ParagraphTextType.qml index 74b155ab..a53ca67e 100644 --- a/client/ui/qml/Controls2/TextTypes/ParagraphTextType.qml +++ b/client/ui/qml/Controls2/TextTypes/ParagraphTextType.qml @@ -1,11 +1,12 @@ import QtQuick Text { - height: 24 + lineHeight: 24 + lineHeightMode: Text.FixedHeight color: "#D7D8DB" font.pixelSize: 16 - font.weight: Font.Normal + font.weight: 400 font.family: "PT Root UI VF" wrapMode: Text.WordWrap diff --git a/client/ui/qml/Controls2/TextTypes/SmallTextType.qml b/client/ui/qml/Controls2/TextTypes/SmallTextType.qml index 96f3342d..d1b24e6a 100644 --- a/client/ui/qml/Controls2/TextTypes/SmallTextType.qml +++ b/client/ui/qml/Controls2/TextTypes/SmallTextType.qml @@ -1,11 +1,12 @@ import QtQuick Text { - height: 20 + lineHeight: 20 + lineHeightMode: Text.FixedHeight color: "#D7D8DB" font.pixelSize: 14 - font.weight: Font.Normal + font.weight: 400 font.family: "PT Root UI VF" wrapMode: Text.WordWrap diff --git a/client/ui/qml/Pages2/PageSettingsAbout.qml b/client/ui/qml/Pages2/PageSettingsAbout.qml index a1bdeb26..01b11bda 100644 --- a/client/ui/qml/Pages2/PageSettingsAbout.qml +++ b/client/ui/qml/Pages2/PageSettingsAbout.qml @@ -19,8 +19,6 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 anchors.topMargin: 20 } diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index 08a5ec0d..b378a6c8 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -8,6 +8,7 @@ import "./" import "../Controls2" import "../Config" import "../Controls2/TextTypes" +import "../Components" PageType { id: root @@ -18,8 +19,6 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 anchors.topMargin: 20 } @@ -52,10 +51,14 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { + selectLanguageDrawer.open() } } - DividerType {} + SelectLanguageDrawer { + id: selectLanguageDrawer + } + LabelWithButtonType { Layout.fillWidth: true diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml index a5445754..ddf9f2ba 100644 --- a/client/ui/qml/Pages2/PageSettingsBackup.qml +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -18,8 +18,6 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 anchors.topMargin: 20 } diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml index ad8524f5..1cf8a1a5 100644 --- a/client/ui/qml/Pages2/PageSettingsConnection.qml +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -17,8 +17,6 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 anchors.topMargin: 20 } diff --git a/client/ui/qml/Pages2/PageSettingsDns.qml b/client/ui/qml/Pages2/PageSettingsDns.qml index 1c989c0c..c51f9092 100644 --- a/client/ui/qml/Pages2/PageSettingsDns.qml +++ b/client/ui/qml/Pages2/PageSettingsDns.qml @@ -18,8 +18,6 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 anchors.topMargin: 20 } diff --git a/client/ui/qml/Pages2/PageSettingsServerInfo.qml b/client/ui/qml/Pages2/PageSettingsServerInfo.qml index aa882aa5..7b6dce68 100644 --- a/client/ui/qml/Pages2/PageSettingsServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsServerInfo.qml @@ -42,14 +42,14 @@ PageType { id: content Layout.topMargin: 20 - Layout.leftMargin: 16 - Layout.rightMargin: 16 BackButtonType { } HeaderType { Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 actionButtonImage: "qrc:/images/controls/edit-3.svg" diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index 7f2b1688..4c6a9839 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -25,14 +25,14 @@ PageType { anchors.right: parent.right anchors.topMargin: 20 - anchors.leftMargin: 16 - anchors.rightMargin: 16 BackButtonType { } HeaderType { Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 actionButtonImage: "qrc:/images/controls/plus.svg" diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 2ef38067..45e4c29c 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -26,19 +26,18 @@ PageType { anchors.left: parent.left anchors.right: parent.right + spacing: 0 + BackButtonType { Layout.topMargin: 20 - Layout.rightMargin: 16 - Layout.leftMargin: 16 } HeaderType { Layout.fillWidth: true - Layout.topMargin: 20 + Layout.topMargin: 8 Layout.rightMargin: 16 Layout.leftMargin: 16 - headerText: "Подключение к серверу" descriptionText: "Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватывать ваши данные.\n Всё в порядке, если код передал друг." @@ -46,7 +45,7 @@ PageType { Header2TextType { Layout.fillWidth: true - Layout.topMargin: 32 + Layout.topMargin: 48 Layout.rightMargin: 16 Layout.leftMargin: 16 diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index 5a2f7d88..487bdbde 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -18,8 +18,6 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 anchors.topMargin: 20 } diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index 19f06d5f..2a6e1909 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -36,8 +36,6 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 anchors.topMargin: 20 } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 5ca75f6f..e593393c 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -63,6 +63,8 @@ PageType { id: backButton Layout.topMargin: 20 + Layout.rightMargin: -16 + Layout.leftMargin: -16 } HeaderType { @@ -107,8 +109,6 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 anchors.topMargin: 16 backButtonFunction: function() { diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index 527df830..edc443d4 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -42,8 +42,6 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 anchors.topMargin: 20 spacing: 16 @@ -52,11 +50,23 @@ PageType { width: parent.width } - HeaderType { + Item { width: parent.width + height: header.implicitHeight - headerText: "Протокол подключения" - descriptionText: "Выберите более приоритетный для вас. Позже можно будет установить остальные протоколы и доп сервисы, вроде DNS-прокси и SFTP." + HeaderType { + id: header + + anchors.fill: parent + + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + width: parent.width + + headerText: "Протокол подключения" + descriptionText: "Выберите более приоритетный для вас. Позже можно будет установить остальные протоколы и доп сервисы, вроде DNS-прокси и SFTP." + } } ListView { @@ -78,8 +88,6 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.rightMargin: -16 - anchors.leftMargin: -16 LabelWithButtonType { id: container diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index ad91303e..78ccfa95 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -34,6 +34,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right + spacing: 0 Image { id: image diff --git a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml index d7dbf43d..504645d1 100644 --- a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml +++ b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml @@ -24,8 +24,6 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 spacing: 16 @@ -35,6 +33,8 @@ PageType { HeaderType { Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 headerText: qsTr("Connection key") descriptionText: qsTr("A line that starts with vpn://...") @@ -45,6 +45,8 @@ PageType { Layout.fillWidth: true Layout.topMargin: 32 + Layout.rightMargin: 16 + Layout.leftMargin: 16 headerText: qsTr("Key") textFieldPlaceholderText: "vpn://" diff --git a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml index c7b774b4..e1b93302 100644 --- a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml +++ b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml @@ -42,8 +42,6 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.rightMargin: 16 - anchors.leftMargin: 16 anchors.topMargin: 20 } diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 1e3d02e8..7f567b8d 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -185,8 +185,6 @@ PageType { 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" From 9b07909ed8fe2b7efa2173d2f6fb07a1588807a6 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 30 Jun 2023 13:45:11 +0900 Subject: [PATCH 038/278] added FlickableType to PageSettingsServerProtocols and PageSettingsServerServices --- client/translations/amneziavpn_ru.ts | 8 +-- .../protocolSettingsController.cpp | 20 +++---- .../ui/qml/Pages2/PageSettingsServerData.qml | 2 +- .../Pages2/PageSettingsServerProtocols.qml | 59 ++++++++++++------- .../qml/Pages2/PageSettingsServerServices.qml | 59 ++++++++++++------- .../qml/Pages2/PageSetupWizardProtocols.qml | 26 ++++---- 6 files changed, 103 insertions(+), 71 deletions(-) diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index b1e7817f..89ca3dcd 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -3114,22 +3114,22 @@ AmneziaVPN detected this profile may contain malicious scripts. Please, carefull main - + It's public key. Private key required - + Ssh log - + App log - + Wrap words diff --git a/client/ui/controllers/protocolSettingsController.cpp b/client/ui/controllers/protocolSettingsController.cpp index 48414021..11b6a904 100644 --- a/client/ui/controllers/protocolSettingsController.cpp +++ b/client/ui/controllers/protocolSettingsController.cpp @@ -1,19 +1,15 @@ #include "protocolSettingsController.h" -ProtocolSettingsController::ProtocolSettingsController( - const QSharedPointer &serversModel, - const QSharedPointer &containersModel, - const std::shared_ptr &settings, - QObject *parent) - : QObject(parent) - , m_serversModel(serversModel) - , m_containersModel(containersModel) - , m_settings(settings) -{} +ProtocolSettingsController::ProtocolSettingsController(const QSharedPointer &serversModel, + const QSharedPointer &containersModel, + const std::shared_ptr &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 containerIndex = m_containersModel->index(m_containersModel->getCurrentlyProcessedContainerIndex()); auto config = m_containersModel->data(containerIndex, ContainersModel::Roles::ConfigRole); + return QByteArray(); } diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 319399dc..5db3ae96 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -50,7 +50,7 @@ PageType { anchors.left: parent.left anchors.right: parent.right - property bool isServerWithWriteAccess: ServersModel.isCurrentlyProcessedServerHasWriteAccess() //todo make it property? + property bool isServerWithWriteAccess: ServersModel.isCurrentlyProcessedServerHasWriteAccess() LabelWithButtonType { visible: content.isServerWithWriteAccess diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocols.qml b/client/ui/qml/Pages2/PageSettingsServerProtocols.qml index 2b67afca..21401bf5 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocols.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocols.qml @@ -20,30 +20,45 @@ PageType { property var installedProtocolsCount - SettingsContainersListView { - id: settingsContainersListView - Connections { - target: ServersModel + FlickableType { + id: fl + anchors.top: parent.top + anchors.bottom: parent.bottom + contentHeight: content.implicitHeight - function onCurrentlyProcessedServerIndexChanged() { - settingsContainersListView.updateContainersModelFilters() + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + SettingsContainersListView { + id: settingsContainersListView + Connections { + target: ServersModel + + function onCurrentlyProcessedServerIndexChanged() { + settingsContainersListView.updateContainersModelFilters() + } + } + + function updateContainersModelFilters() { + if (ServersModel.isCurrentlyProcessedServerHasWriteAccess()) { + proxyContainersModel.filters = ContainersModelFilters.getWriteAccessProtocolsListFilters() + } else { + proxyContainersModel.filters = ContainersModelFilters.getReadAccessProtocolsListFilters() + } + root.installedProtocolsCount = proxyContainersModel.count + } + + model: SortFilterProxyModel { + id: proxyContainersModel + sourceModel: ContainersModel + } + + Component.onCompleted: updateContainersModelFilters() } } - - function updateContainersModelFilters() { - if (ServersModel.isCurrentlyProcessedServerHasWriteAccess()) { - proxyContainersModel.filters = ContainersModelFilters.getWriteAccessProtocolsListFilters() - } else { - proxyContainersModel.filters = ContainersModelFilters.getReadAccessProtocolsListFilters() - } - root.installedProtocolsCount = proxyContainersModel.count - } - - model: SortFilterProxyModel { - id: proxyContainersModel - sourceModel: ContainersModel - } - - Component.onCompleted: updateContainersModelFilters() } } diff --git a/client/ui/qml/Pages2/PageSettingsServerServices.qml b/client/ui/qml/Pages2/PageSettingsServerServices.qml index 282e7e9e..4f832651 100644 --- a/client/ui/qml/Pages2/PageSettingsServerServices.qml +++ b/client/ui/qml/Pages2/PageSettingsServerServices.qml @@ -20,30 +20,45 @@ PageType { property var installedServicesCount - SettingsContainersListView { - id: settingsContainersListView - Connections { - target: ServersModel + FlickableType { + id: fl + anchors.top: parent.top + anchors.bottom: parent.bottom + contentHeight: content.implicitHeight - function onCurrentlyProcessedServerIndexChanged() { - settingsContainersListView.updateContainersModelFilters() + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + SettingsContainersListView { + id: settingsContainersListView + Connections { + target: ServersModel + + function onCurrentlyProcessedServerIndexChanged() { + settingsContainersListView.updateContainersModelFilters() + } + } + + function updateContainersModelFilters() { + if (ServersModel.isCurrentlyProcessedServerHasWriteAccess()) { + proxyContainersModel.filters = ContainersModelFilters.getWriteAccessServicesListFilters() + } else { + proxyContainersModel.filters = ContainersModelFilters.getReadAccessServicesListFilters() + } + root.installedServicesCount = proxyContainersModel.count + } + + model: SortFilterProxyModel { + id: proxyContainersModel + sourceModel: ContainersModel + } + + Component.onCompleted: updateContainersModelFilters() } } - - function updateContainersModelFilters() { - if (ServersModel.isCurrentlyProcessedServerHasWriteAccess()) { - proxyContainersModel.filters = ContainersModelFilters.getWriteAccessServicesListFilters() - } else { - proxyContainersModel.filters = ContainersModelFilters.getReadAccessServicesListFilters() - } - root.installedServicesCount = proxyContainersModel.count - } - - model: SortFilterProxyModel { - id: proxyContainersModel - sourceModel: ContainersModel - } - - Component.onCompleted: updateContainersModelFilters() } } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index edc443d4..f343fabf 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -26,15 +26,27 @@ PageType { roleName: "isSupported" value: true } - ] } + ColumnLayout { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + } + } + FlickableType { id: fl - anchors.top: parent.top + anchors.top: backButton.top anchors.bottom: parent.bottom - contentHeight: content.height + contentHeight: content.implicitHeight + content.anchors.topMargin + content.anchors.bottomMargin Column { id: content @@ -42,13 +54,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 - - spacing: 16 - - BackButtonType { - width: parent.width - } + anchors.bottomMargin: 20 Item { width: parent.width From 35660ff5e75eb7bc6d31e93a09b7a7cf0988d51d Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 30 Jun 2023 18:14:47 +0300 Subject: [PATCH 039/278] speed optimization of ui on windows --- .../protocolSettingsController.cpp | 1 + client/ui/models/servers_model.cpp | 43 +++++++++++++------ client/ui/models/servers_model.h | 4 +- .../ui/qml/Controls2/VerticalRadioButton.qml | 5 --- 4 files changed, 33 insertions(+), 20 deletions(-) diff --git a/client/ui/controllers/protocolSettingsController.cpp b/client/ui/controllers/protocolSettingsController.cpp index 48414021..97d39d5d 100644 --- a/client/ui/controllers/protocolSettingsController.cpp +++ b/client/ui/controllers/protocolSettingsController.cpp @@ -16,4 +16,5 @@ QByteArray ProtocolSettingsController::getOpenVpnConfig() auto containerIndex = m_containersModel->index( m_containersModel->getCurrentlyProcessedContainerIndex()); auto config = m_containersModel->data(containerIndex, ContainersModel::Roles::ConfigRole); + return QByteArray(); } diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index b427f15b..866edca4 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -5,7 +5,7 @@ ServersModel::ServersModel(std::shared_ptr settings, QObject *parent) { m_servers = m_settings->serversArray(); m_defaultServerIndex = m_settings->defaultServerIndex(); - m_currenlyProcessedServerIndex = m_defaultServerIndex; + m_currentlyProcessedServerIndex = m_defaultServerIndex; } int ServersModel::rowCount(const QModelIndex &parent) const @@ -44,6 +44,8 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const return QVariant(); } + qDebug() << "d"; + const QJsonObject server = m_servers.at(index.row()).toObject(); switch (role) { @@ -55,12 +57,12 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const return description; } case HostNameRole: return server.value(config_key::hostName).toString(); - case CredentialsRole: return QVariant::fromValue(m_settings->serverCredentials(index.row())); - case CredentialsLoginRole: return m_settings->serverCredentials(index.row()).userName; + case CredentialsRole: return QVariant::fromValue(serverCredentials(index.row())); + case CredentialsLoginRole: return serverCredentials(index.row()).userName; case IsDefaultRole: return index.row() == m_defaultServerIndex; - case IsCurrentlyProcessedRole: return index.row() == m_currenlyProcessedServerIndex; + case IsCurrentlyProcessedRole: return index.row() == m_currentlyProcessedServerIndex; case HasWriteAccessRole: { - auto credentials = m_settings->serverCredentials(index.row()); + auto credentials = serverCredentials(index.row()); return (!credentials.userName.isEmpty() && !credentials.secretData.isEmpty()); } case ContainsAmneziaDnsRole: { @@ -97,28 +99,28 @@ const int ServersModel::getServersCount() void ServersModel::setCurrentlyProcessedServerIndex(const int index) { - m_currenlyProcessedServerIndex = index; - emit currentlyProcessedServerIndexChanged(m_currenlyProcessedServerIndex); + m_currentlyProcessedServerIndex = index; + emit currentlyProcessedServerIndexChanged(m_currentlyProcessedServerIndex); } int ServersModel::getCurrentlyProcessedServerIndex() { - return m_currenlyProcessedServerIndex; + return m_currentlyProcessedServerIndex; } bool ServersModel::isDefaultServerCurrentlyProcessed() { - return m_defaultServerIndex == m_currenlyProcessedServerIndex; + return m_defaultServerIndex == m_currentlyProcessedServerIndex; } bool ServersModel::isCurrentlyProcessedServerHasWriteAccess() { - return qvariant_cast(data(m_currenlyProcessedServerIndex, HasWriteAccessRole)); + return qvariant_cast(data(m_currentlyProcessedServerIndex, HasWriteAccessRole)); } bool ServersModel::isDefaultServerHasWriteAccess() { - return qvariant_cast(data(m_currenlyProcessedServerIndex, HasWriteAccessRole)); + return qvariant_cast(data(m_currentlyProcessedServerIndex, HasWriteAccessRole)); } void ServersModel::addServer(const QJsonObject &server) @@ -132,12 +134,12 @@ void ServersModel::addServer(const QJsonObject &server) void ServersModel::removeServer() { beginResetModel(); - m_settings->removeServer(m_currenlyProcessedServerIndex); + m_settings->removeServer(m_currentlyProcessedServerIndex); m_servers = m_settings->serversArray(); - if (m_settings->defaultServerIndex() == m_currenlyProcessedServerIndex) { + if (m_settings->defaultServerIndex() == m_currentlyProcessedServerIndex) { setDefaultServerIndex(0); - } else if (m_settings->defaultServerIndex() > m_currenlyProcessedServerIndex) { + } else if (m_settings->defaultServerIndex() > m_currentlyProcessedServerIndex) { setDefaultServerIndex(m_settings->defaultServerIndex() - 1); } @@ -167,3 +169,16 @@ QHash ServersModel::roleNames() const roles[ContainsAmneziaDnsRole] = "containsAmneziaDns"; return roles; } + +ServerCredentials ServersModel::serverCredentials(int index) const +{ + const QJsonObject &s = m_servers.at(index).toObject(); + + ServerCredentials credentials; + credentials.hostName = s.value(config_key::hostName).toString(); + credentials.userName = s.value(config_key::userName).toString(); + credentials.secretData = s.value(config_key::password).toString(); + credentials.port = s.value(config_key::port).toInt(); + + return credentials; +} diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index 0ec78e7e..f149d85b 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -58,12 +58,14 @@ signals: void defaultServerIndexChanged(); private: + ServerCredentials serverCredentials(int index) const; + QJsonArray m_servers; std::shared_ptr m_settings; int m_defaultServerIndex; - int m_currenlyProcessedServerIndex; + int m_currentlyProcessedServerIndex; }; #endif // SERVERSMODEL_H diff --git a/client/ui/qml/Controls2/VerticalRadioButton.qml b/client/ui/qml/Controls2/VerticalRadioButton.qml index 7f2411fb..c833ca27 100644 --- a/client/ui/qml/Controls2/VerticalRadioButton.qml +++ b/client/ui/qml/Controls2/VerticalRadioButton.qml @@ -18,11 +18,6 @@ RadioButton { property string textColor: "#D7D8DB" property string selectedTextColor: "#FBB26A" - property string pressedBorderColor: Qt.rgba(251/255, 178/255, 106/255, 0.3) - property string selectedBorderColor: "#FBB26A" - property string defaultBodredColor: "transparent" - property int borderWidth: 0 - property string imageSource property bool showImage From 43261f8469ae9ab7327060de65ad983e41009e59 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 4 Jul 2023 09:58:19 +0900 Subject: [PATCH 040/278] added busy indicator component - replaced the image of the connect button with native rendering --- client/resources.qrc | 1 + client/translations/amneziavpn_ru.ts | 6 +- client/ui/controllers/pageController.h | 1 + client/ui/models/servers_model.cpp | 2 - client/ui/qml/Components/ConnectButton.qml | 117 +++++++++++++----- .../qml/Components/ShareConnectionDrawer.qml | 19 +-- client/ui/qml/Controls2/BusyIndicatorType.qml | 68 ++++++++++ .../ui/qml/Pages2/PageSettingsServerData.qml | 2 + client/ui/qml/Pages2/PageShare.qml | 11 +- client/ui/qml/Pages2/PageStart.qml | 12 ++ 10 files changed, 197 insertions(+), 42 deletions(-) create mode 100644 client/ui/qml/Controls2/BusyIndicatorType.qml diff --git a/client/resources.qrc b/client/resources.qrc index 357cb40c..b7bdd463 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -270,5 +270,6 @@ ui/qml/Controls2/TextTypes/SmallTextType.qml ui/qml/Filters/ContainersModelFilters.qml ui/qml/Components/SelectLanguageDrawer.qml + ui/qml/Controls2/BusyIndicatorType.qml diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index 89ca3dcd..197c9d40 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -1275,17 +1275,17 @@ And if you don't like the app, all the more support it - the donation will PageSettingsApplication - + Application - + Language - + Reset settings and remove all data from the application diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 384d3c8d..f67da275 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -63,6 +63,7 @@ signals: void replaceStartPage(); void showErrorMessage(QString errorMessage); void showInfoMessage(QString message); + void showBusyIndicator(bool visible); private: QSharedPointer m_serversModel; diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 866edca4..1bacdd45 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -44,8 +44,6 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const return QVariant(); } - qDebug() << "d"; - const QJsonObject server = m_servers.at(index.row()).toObject(); switch (role) { diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml index 1f27973a..f8e29c47 100644 --- a/client/ui/qml/Components/ConnectButton.qml +++ b/client/ui/qml/Components/ConnectButton.qml @@ -1,12 +1,21 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import QtQuick.Shapes +import Qt5Compat.GraphicalEffects import ConnectionState 1.0 Button { id: root + property string defaultButtonColor: "#D7D8DB" + property string progressButtonColor: "#D7D8DB" + property string connectedButtonColor: "#FBB26A" + + implicitWidth: 190 + implicitHeight: 190 + Connections { target: ConnectionController @@ -17,48 +26,98 @@ Button { text: ConnectionController.connectionStateText - enabled: !ConnectionController.isConnectionInProgress +// enabled: !ConnectionController.isConnectionInProgress background: Item { - clip: true + implicitWidth: parent.width + implicitHeight: parent.height + transformOrigin: Item.Center - implicitHeight: border.implicitHeight - implicitWidth: border.implicitWidth + Shape { + id: backgroundCircle + width: parent.implicitWidth + height: parent.implicitHeight + anchors.bottom: parent.bottom + anchors.right: parent.right + layer.enabled: true + layer.samples: 4 + layer.effect: DropShadow { + anchors.fill: backgroundCircle + horizontalOffset: 0 + verticalOffset: 0 + radius: 10 + samples: 25 + color: "#FBB26A" + source: backgroundCircle + } - Image { - id: border + ShapePath { + fillColor: "transparent" + strokeColor: { + if (ConnectionController.isConnectionInProgress) { + return Qt.rgba(251/255, 178/255, 106/255, 1) + } else if (ConnectionController.isConnected) { + return connectedButtonColor + } else { + return defaultButtonColor + } + } + strokeWidth: 3 + capStyle: ShapePath.RoundCap - source: { - if (ConnectionController.isConnectionInProgress) { - return "/images/connectionProgress.svg" - } else if (ConnectionController.isConnected) { - return "/images/connectionOff.svg" - } else { - return "/images/connectionOn.svg" + PathAngleArc { + centerX: backgroundCircle.width / 2 + centerY: backgroundCircle.height / 2 + radiusX: 93 + radiusY: 93 + startAngle: 0 + sweepAngle: 360 + } + } + + MouseArea { + anchors.fill: parent + + cursorShape: Qt.PointingHandCursor + enabled: false + } + } + + Shape { + id: shape + width: parent.implicitWidth + height: parent.implicitHeight + anchors.bottom: parent.bottom + anchors.right: parent.right + layer.enabled: true + layer.samples: 4 + + visible: ConnectionController.isConnectionInProgress + + ShapePath { + fillColor: "transparent" + strokeColor: "#D7D8DB" + strokeWidth: 3 + capStyle: ShapePath.RoundCap + + PathAngleArc { + centerX: shape.width / 2 + centerY: shape.height / 2 + radiusX: 93 + radiusY: 93 + startAngle: 245 + sweepAngle: -180 } } RotationAnimator { - id: connectionProccess - - target: border + target: shape running: ConnectionController.isConnectionInProgress from: 0 to: 360 loops: Animation.Infinite - duration: 1250 + duration: 1000 } - - Behavior on source { - PropertyAnimation { duration: 200 } - } - } - - MouseArea { - anchors.fill: parent - - cursorShape: Qt.PointingHandCursor - enabled: false } } @@ -69,7 +128,7 @@ Button { font.weight: 700 font.pixelSize: 20 - color: "#D7D8DB" + color: ConnectionController.isConnected ? connectedButtonColor : defaultButtonColor text: root.text horizontalAlignment: Text.AlignHCenter diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 99e172cc..b677c5b1 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -17,6 +17,7 @@ DrawerType { property alias headerText: header.headerText property alias configContentHeaderText: configContentHeader.headerText + property alias contentVisible: content.visible width: parent.width height: parent.height * 0.9 @@ -24,8 +25,18 @@ DrawerType { Item { anchors.fill: parent - FlickableType { + Header2Type { + id: header anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 20 + anchors.leftMargin: 16 + anchors.rightMargin: 16 + } + + FlickableType { + anchors.top: header.bottom anchors.bottom: parent.bottom contentHeight: content.height + 32 @@ -36,15 +47,9 @@ DrawerType { 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 diff --git a/client/ui/qml/Controls2/BusyIndicatorType.qml b/client/ui/qml/Controls2/BusyIndicatorType.qml new file mode 100644 index 00000000..7e92998c --- /dev/null +++ b/client/ui/qml/Controls2/BusyIndicatorType.qml @@ -0,0 +1,68 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Shapes + +Popup { + id: root + anchors.centerIn: parent + + modal: true + closePolicy: Popup.NoAutoClose + + visible: false + + Overlay.modal: Rectangle { + color: Qt.rgba(14/255, 14/255, 17/255, 0.8) + } + + background: Rectangle { + color: "transparent" + } + + BusyIndicator { + id: busyIndicator + + visible: true + running: true + + contentItem: Item { + implicitWidth: 46 + implicitHeight: 46 + transformOrigin: Item.Center + + Shape { + id: shape + width: parent.implicitWidth + height: parent.implicitHeight + anchors.bottom: parent.bottom + anchors.right: parent.right + layer.enabled: true + layer.samples: 4 + + ShapePath { + fillColor: "transparent" + strokeColor: "#787878" + strokeWidth: 3 + capStyle: ShapePath.RoundCap + + PathAngleArc { + centerX: shape.width / 2 + centerY: shape.height / 2 + radiusX: 18 + radiusY: 18 + startAngle: 225 + sweepAngle: -90 + } + } + RotationAnimator { + target: shape + running: busyIndicator.visible && busyIndicator.running + from: 0 + to: 360 + loops: Animation.Infinite + duration: 1250 + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 5db3ae96..c5af47e0 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -88,7 +88,9 @@ PageType { descriptionText: qsTr("Добавим их в приложение, если они не отображались") clickedFunction: function() { + PageController.showBusyIndicator(true) InstallController.scanServerForInstalledContainers() + PageController.showBusyIndicator(false) } } diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 7f567b8d..decd461a 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -20,11 +20,21 @@ PageType { target: ExportController function onGenerateConfig(isFullAccess) { + shareConnectionDrawer.open() + shareConnectionDrawer.contentVisible = false + PageController.showBusyIndicator(true) if (isFullAccess) { ExportController.generateFullAccessConfig() } else { ExportController.generateConnectionConfig() } + PageController.showBusyIndicator(false) + shareConnectionDrawer.contentVisible = true + } + + function onExportErrorOccurred(errorMessage) { + shareConnectionDrawer.close() + PageController.showErrorMessage(errorMessage) } } @@ -328,7 +338,6 @@ PageType { } else { ExportController.generateConfig(true) } - shareConnectionDrawer.visible = true } } } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 76450b16..09bebaa1 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -29,6 +29,12 @@ PageType { popupErrorMessage.popupErrorMessageText = errorMessage popupErrorMessage.open() } + + function onShowBusyIndicator(visible) { + busyIndicator.visible = visible + tabBarStackView.enabled = !visible + tabBar.enabled = !visible + } } StackViewType { @@ -130,4 +136,10 @@ PageType { id: popupErrorMessage } } + + BusyIndicatorType { + id: busyIndicator + anchors.centerIn: parent + z: 1 + } } From a97417fd38cfaea7cbcafdc30ccb9b85d6eff6a1 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 5 Jul 2023 10:15:38 +0900 Subject: [PATCH 041/278] added config export in native format openvpn and wireguard --- client/ui/controllers/exportController.cpp | 101 ++++++++++++++++-- client/ui/controllers/exportController.h | 16 +-- client/ui/qml/Components/ConnectButton.qml | 2 +- .../qml/Components/ShareConnectionDrawer.qml | 19 ++-- client/ui/qml/Pages2/PageShare.qml | 38 ++++--- 5 files changed, 137 insertions(+), 39 deletions(-) diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index 04264624..5cd9a83d 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -9,6 +9,8 @@ #include #include +#include "configurators/openvpn_configurator.h" +#include "configurators/wireguard_configurator.h" #include "qrcodegen.hpp" #include "core/errorstrings.h" @@ -27,14 +29,17 @@ ExportController::ExportController(const QSharedPointer &serversMo void ExportController::generateFullAccessConfig() { + clearPreviousConfig(); + 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_rawConfig = QString("vpn://%1") + .arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding + | QByteArray::OmitTrailingEquals))); + m_formattedConfig = m_rawConfig; m_qrCodes = generateQrCodeImageSeries(compressedConfig); emit exportConfigChanged(); @@ -42,6 +47,8 @@ void ExportController::generateFullAccessConfig() void ExportController::generateConnectionConfig() { + clearPreviousConfig(); + int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); ServerCredentials credentials = qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); @@ -81,17 +88,86 @@ void ExportController::generateConnectionConfig() QByteArray compressedConfig = QJsonDocument(config).toJson(); compressedConfig = qCompress(compressedConfig, 8); - m_amneziaCode = QString("vpn://%1") - .arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding - | QByteArray::OmitTrailingEquals))); + m_rawConfig = QString("vpn://%1") + .arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding + | QByteArray::OmitTrailingEquals))); + m_formattedConfig = m_rawConfig; m_qrCodes = generateQrCodeImageSeries(compressedConfig); emit exportConfigChanged(); } -QString ExportController::getAmneziaCode() +void ExportController::generateOpenVpnConfig() { - return m_amneziaCode; + clearPreviousConfig(); + + int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + ServerCredentials credentials = + qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); + + DockerContainer container = static_cast(m_containersModel->getCurrentlyProcessedContainerIndex()); + QModelIndex containerModelIndex = m_containersModel->index(container); + QJsonObject containerConfig = + qvariant_cast(m_containersModel->data(containerModelIndex, ContainersModel::Roles::ConfigRole)); + containerConfig.insert(config_key::container, ContainerProps::containerToString(container)); + + ErrorCode errorCode = ErrorCode::NoError; + QString config = + m_configurator->openVpnConfigurator->genOpenVpnConfig(credentials, container, containerConfig, &errorCode); + if (errorCode) { + emit exportErrorOccurred(errorString(errorCode)); + return; + } + config = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::OpenVpn, config); + + m_rawConfig = config; + + auto configJson = QJsonDocument::fromJson(config.toUtf8()).object(); + QStringList lines = configJson.value(config_key::config).toString().replace("\r", "").split("\n"); + for (const QString &line : lines) { + m_formattedConfig.append(line + "\n"); + } + + emit exportConfigChanged(); +} + +void ExportController::generateWireGuardConfig() +{ + clearPreviousConfig(); + + int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + ServerCredentials credentials = + qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); + + DockerContainer container = static_cast(m_containersModel->getCurrentlyProcessedContainerIndex()); + QModelIndex containerModelIndex = m_containersModel->index(container); + QJsonObject containerConfig = + qvariant_cast(m_containersModel->data(containerModelIndex, ContainersModel::Roles::ConfigRole)); + containerConfig.insert(config_key::container, ContainerProps::containerToString(container)); + + ErrorCode errorCode = ErrorCode::NoError; + QString config = m_configurator->wireguardConfigurator->genWireguardConfig(credentials, container, containerConfig, + &errorCode); + if (errorCode) { + emit exportErrorOccurred(errorString(errorCode)); + return; + } + config = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::WireGuard, config); + + m_rawConfig = config; + + auto configJson = QJsonDocument::fromJson(config.toUtf8()).object(); + QStringList lines = configJson.value(config_key::config).toString().replace("\r", "").split("\n"); + for (const QString &line : lines) { + m_formattedConfig.append(line + "\n"); + } + + emit exportConfigChanged(); +} + +QString ExportController::getFormattedConfig() +{ + return m_formattedConfig; } QList ExportController::getQrCodes() @@ -117,7 +193,7 @@ void ExportController::saveFile() QFile save(fileName.toLocalFile()); save.open(QIODevice::WriteOnly); - save.write(m_amneziaCode.toUtf8()); + save.write(m_rawConfig.toUtf8()); save.close(); QFileInfo fi(fileName.toLocalFile()); @@ -154,3 +230,10 @@ int ExportController::getQrCodesCount() { return m_qrCodes.size(); } + +void ExportController::clearPreviousConfig() +{ + m_rawConfig.clear(); + m_formattedConfig.clear(); + m_qrCodes.clear(); +} diff --git a/client/ui/controllers/exportController.h b/client/ui/controllers/exportController.h index 63997efd..85144978 100644 --- a/client/ui/controllers/exportController.h +++ b/client/ui/controllers/exportController.h @@ -14,24 +14,25 @@ public: explicit ExportController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, const std::shared_ptr &settings, - const std::shared_ptr &configurator, - QObject *parent = nullptr); + const std::shared_ptr &configurator, QObject *parent = nullptr); Q_PROPERTY(QList qrCodes READ getQrCodes NOTIFY exportConfigChanged) Q_PROPERTY(int qrCodesCount READ getQrCodesCount NOTIFY exportConfigChanged) - Q_PROPERTY(QString amneziaCode READ getAmneziaCode NOTIFY exportConfigChanged) + Q_PROPERTY(QString formattedConfig READ getFormattedConfig NOTIFY exportConfigChanged) public slots: void generateFullAccessConfig(); void generateConnectionConfig(); + void generateOpenVpnConfig(); + void generateWireGuardConfig(); - QString getAmneziaCode(); + QString getFormattedConfig(); QList getQrCodes(); void saveFile(); signals: - void generateConfig(bool isFullAccess); + void generateConfig(int type); void exportErrorOccurred(QString errorMessage); void exportConfigChanged(); @@ -42,12 +43,15 @@ private: int getQrCodesCount(); + void clearPreviousConfig(); + QSharedPointer m_serversModel; QSharedPointer m_containersModel; std::shared_ptr m_settings; std::shared_ptr m_configurator; - QString m_amneziaCode; + QString m_rawConfig; + QString m_formattedConfig; QList m_qrCodes; }; diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml index f8e29c47..85cc345a 100644 --- a/client/ui/qml/Components/ConnectButton.qml +++ b/client/ui/qml/Components/ConnectButton.qml @@ -55,7 +55,7 @@ Button { fillColor: "transparent" strokeColor: { if (ConnectionController.isConnectionInProgress) { - return Qt.rgba(251/255, 178/255, 106/255, 1) + return "#261E1A" } else if (ConnectionController.isConnected) { return connectedButtonColor } else { diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index b677c5b1..d5ed1029 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -137,7 +137,7 @@ DrawerType { Layout.topMargin: 16 } - TextField { + TextArea { Layout.fillWidth: true Layout.topMargin: 16 Layout.bottomMargin: 16 @@ -147,16 +147,17 @@ DrawerType { height: 24 color: "#D7D8DB" + selectionColor: "#412102" + selectedTextColor: "#D7D8DB" font.pixelSize: 16 font.weight: Font.Medium font.family: "PT Root UI VF" - text: ExportController.amneziaCode + text: ExportController.formattedConfig wrapMode: Text.Wrap - readOnly: true background: Rectangle { color: "transparent" } @@ -186,11 +187,13 @@ DrawerType { running: ExportController.qrCodesCount > 0 repeat: true onTriggered: { - index++ - if (index >= ExportController.qrCodesCount) { - index = 0 + if (ExportController.qrCodesCount > 0) { + index++ + if (index >= ExportController.qrCodesCount) { + index = 0 + } + parent.source = ExportController.qrCodes[index] } - parent.source = ExportController.qrCodes[index] } } @@ -205,6 +208,8 @@ DrawerType { Layout.topMargin: 24 Layout.bottomMargin: 32 + visible: ExportController.qrCodesCount > 0 + horizontalAlignment: Text.AlignHCenter text: qsTr("To read the QR code in the Amnezia app, select \"Add Server\" → \"I have connection details\"") } diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index decd461a..c83b59d5 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -16,18 +16,30 @@ import "../Components" PageType { id: root + enum ConfigType { + AmneziaConnection, + AmenziaFullAccess, + OpenVpn, + WireGuard + } + Connections { target: ExportController - function onGenerateConfig(isFullAccess) { + function onGenerateConfig(type) { shareConnectionDrawer.open() shareConnectionDrawer.contentVisible = false PageController.showBusyIndicator(true) - if (isFullAccess) { - ExportController.generateFullAccessConfig() - } else { - ExportController.generateConnectionConfig() + + console.log(type) + + switch (type) { + case PageShare.ConfigType.AmneziaConnection: ExportController.generateConnectionConfig(); break; + case PageShare.ConfigType.AmenziaFullAccess: ExportController.generateFullAccessConfig(); break; + case PageShare.ConfigType.OpenVpn: ExportController.generateOpenVpnConfig(); break; + case PageShare.ConfigType.WireGuard: ExportController.generateWireGuardConfig(); break; } + PageController.showBusyIndicator(false) shareConnectionDrawer.contentVisible = true } @@ -46,23 +58,17 @@ PageType { QtObject { id: amneziaConnectionFormat property string name: qsTr("For the AmnesiaVPN app") - property var func: function() { - ExportController.generateConfig(false) - } + property var type: PageShare.ConfigType.AmneziaConnection } QtObject { id: openVpnConnectionFormat property string name: qsTr("OpenVpn native format") - property var func: function() { - console.log("Item 3 clicked") - } + property var type: PageShare.ConfigType.OpenVpn } QtObject { id: wireGuardConnectionFormat property string name: qsTr("WireGuard native format") - property var func: function() { - console.log("Item 3 clicked") - } + property var type: PageShare.ConfigType.WireGuard } FlickableType { @@ -334,9 +340,9 @@ PageType { onClicked: { if (accessTypeSelector.currentIndex === 0) { - root.connectionTypesModel[accessTypeSelector.currentIndex].func() + ExportController.generateConfig(root.connectionTypesModel[exportTypeSelector.currentIndex].type) } else { - ExportController.generateConfig(true) + ExportController.generateConfig(PageShare.ConfigType.AmneziaFullAccess) } } } From c13b9754eb2fb90f0e00547004d03bb3f7d2fee7 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 13 Jul 2023 11:29:26 +0900 Subject: [PATCH 042/278] added protocol settings pages and models for openvpn, cloak and shadowsocks --- client/CMakeLists.txt | 4 +- client/amnezia_application.cpp | 86 ++-- client/amnezia_application.h | 20 + client/protocols/protocols_defs.h | 406 ++++++++------- client/resources.qrc | 4 +- client/ui/controllers/installController.cpp | 27 +- client/ui/controllers/installController.h | 4 + client/ui/controllers/pageController.h | 8 +- client/ui/models/containers_model.cpp | 38 +- client/ui/models/containers_model.h | 4 + client/ui/models/languageModel.cpp | 10 +- .../ui/models/protocols/cloakConfigModel.cpp | 81 +++ client/ui/models/protocols/cloakConfigModel.h | 40 ++ .../ui/models/protocols/ikev2ConfigModel.cpp | 76 +++ client/ui/models/protocols/ikev2ConfigModel.h | 39 ++ .../models/protocols/openvpnConfigModel.cpp | 152 ++++++ .../ui/models/protocols/openvpnConfigModel.h | 52 ++ .../protocols/shadowsocksConfigModel.cpp | 76 +++ .../models/protocols/shadowsocksConfigModel.h | 39 ++ .../models/protocols/wireguardConfigModel.cpp | 70 +++ .../models/protocols/wireguardConfigModel.h | 39 ++ client/ui/models/protocols_model.cpp | 75 +-- client/ui/models/protocols_model.h | 30 +- client/ui/models/servers_model.cpp | 1 + .../ui/pages_logic/GeneralSettingsLogic.cpp | 14 +- .../ui/pages_logic/ServerContainersLogic.cpp | 62 +-- .../Components/Protocols/OpenVpnSettings.qml | 147 ------ .../Components/SettingsContainersListView.qml | 29 +- .../qml/Components/ShareConnectionDrawer.qml | 8 +- .../qml/Components/TransportProtoSelector.qml | 4 + client/ui/qml/Controls2/CheckBoxType.qml | 46 +- client/ui/qml/Controls2/DropDownType.qml | 33 +- .../qml/Controls2/HorizontalRadioButton.qml | 25 +- .../qml/Controls2/TextFieldWithHeaderType.qml | 30 +- client/ui/qml/Pages2/PageHome.qml | 1 - .../qml/Pages2/PageProtocolCloakSettings.qml | 176 +++++++ .../Pages2/PageProtocolOpenVpnSettings.qml | 465 ++++++++++++++++++ .../PageProtocolShadowSocksSettings.qml | 162 ++++++ .../qml/Pages2/PageSettingsServerProtocol.qml | 93 +++- .../qml/Pages2/PageSetupWizardInstalling.qml | 4 - .../qml/Pages2/PageSetupWizardProtocols.qml | 2 +- client/ui/qml/Pages2/PageShare.qml | 24 +- 42 files changed, 2130 insertions(+), 576 deletions(-) create mode 100644 client/ui/models/protocols/cloakConfigModel.cpp create mode 100644 client/ui/models/protocols/cloakConfigModel.h create mode 100644 client/ui/models/protocols/ikev2ConfigModel.cpp create mode 100644 client/ui/models/protocols/ikev2ConfigModel.h create mode 100644 client/ui/models/protocols/openvpnConfigModel.cpp create mode 100644 client/ui/models/protocols/openvpnConfigModel.h create mode 100644 client/ui/models/protocols/shadowsocksConfigModel.cpp create mode 100644 client/ui/models/protocols/shadowsocksConfigModel.h create mode 100644 client/ui/models/protocols/wireguardConfigModel.cpp create mode 100644 client/ui/models/protocols/wireguardConfigModel.h delete mode 100644 client/ui/qml/Components/Protocols/OpenVpnSettings.qml create mode 100644 client/ui/qml/Pages2/PageProtocolCloakSettings.qml create mode 100644 client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml create mode 100644 client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index ad9a4cf4..be1c3b92 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -129,8 +129,8 @@ file(GLOB_RECURSE PAGE_LOGIC_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/ file(GLOB CONFIGURATORS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/configurators/*.h) file(GLOB CONFIGURATORS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/configurators/*.cpp) -file(GLOB UI_MODELS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/models/*.h) -file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/models/*.cpp) +file(GLOB UI_MODELS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/models/*.h ${CMAKE_CURRENT_LIST_DIR}/ui/models/protocols/*.h) +file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/models/*.cpp ${CMAKE_CURRENT_LIST_DIR}/ui/models/protocols/*.cpp) file(GLOB UI_CONTROLLERS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.h) file(GLOB UI_CONTROLLERS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.cpp) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 7c3b466b..436dfc3f 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -79,38 +79,12 @@ void AmneziaApplication::init() m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance()); // - m_containersModel.reset(new ContainersModel(m_settings, this)); - m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get()); - - m_serversModel.reset(new ServersModel(m_settings, this)); - m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get()); - connect(m_serversModel.get(), &ServersModel::currentlyProcessedServerIndexChanged, m_containersModel.get(), - &ContainersModel::setCurrentlyProcessedServerIndex); - - m_languageModel.reset(new LanguageModel(m_settings, this)); - m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get()); - connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &AmneziaApplication::updateTranslator); m_configurator = std::shared_ptr(new VpnConfigurator(m_settings, this)); m_vpnConnection.reset(new VpnConnection(m_settings, m_configurator)); - m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel, m_vpnConnection)); - m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get()); - - m_pageController.reset(new PageController(m_serversModel)); - m_engine->rootContext()->setContextProperty("PageController", m_pageController.get()); - - m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_settings)); - m_engine->rootContext()->setContextProperty("InstallController", m_installController.get()); - - m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings)); - m_engine->rootContext()->setContextProperty("ImportController", m_importController.get()); - - m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_settings, m_configurator)); - m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get()); - - m_settingsController.reset(new SettingsController(m_serversModel, m_containersModel, m_settings)); - m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get()); + initModels(); + initControllers(); // @@ -244,3 +218,59 @@ QQmlApplicationEngine *AmneziaApplication::qmlEngine() const { return m_engine; } + +void AmneziaApplication::initModels() +{ + m_containersModel.reset(new ContainersModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get()); + + m_serversModel.reset(new ServersModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get()); + connect(m_serversModel.get(), &ServersModel::currentlyProcessedServerIndexChanged, m_containersModel.get(), + &ContainersModel::setCurrentlyProcessedServerIndex); + + m_languageModel.reset(new LanguageModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get()); + connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &AmneziaApplication::updateTranslator); + + m_protocolsModel.reset(new ProtocolsModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get()); + + m_openVpnConfigModel.reset(new OpenVpnConfigModel(this)); + m_engine->rootContext()->setContextProperty("OpenVpnConfigModel", m_openVpnConfigModel.get()); + + m_shadowSocksConfigModel.reset(new ShadowSocksConfigModel(this)); + m_engine->rootContext()->setContextProperty("ShadowSocksConfigModel", m_shadowSocksConfigModel.get()); + + m_cloakConfigModel.reset(new CloakConfigModel(this)); + m_engine->rootContext()->setContextProperty("CloakConfigModel", m_cloakConfigModel.get()); + + m_wireguardConfigModel.reset(new WireGuardConfigModel(this)); + m_engine->rootContext()->setContextProperty("WireGuardConfigModel", m_wireguardConfigModel.get()); + +#ifdef Q_OS_WINDOWS + m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this)); + m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get()); +#endif +} + +void AmneziaApplication::initControllers() +{ + m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel, m_vpnConnection)); + m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get()); + + m_pageController.reset(new PageController(m_serversModel)); + m_engine->rootContext()->setContextProperty("PageController", m_pageController.get()); + + m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_settings)); + m_engine->rootContext()->setContextProperty("InstallController", m_installController.get()); + + m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings)); + m_engine->rootContext()->setContextProperty("ImportController", m_importController.get()); + + m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_settings, m_configurator)); + m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get()); + + m_settingsController.reset(new SettingsController(m_serversModel, m_containersModel, m_settings)); + m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get()); +} diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 4e9cc348..fabc7818 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -21,6 +21,14 @@ #include "ui/controllers/settingsController.h" #include "ui/models/containers_model.h" #include "ui/models/languageModel.h" +#include "ui/models/protocols/cloakConfigModel.h" +#ifdef Q_OS_WINDOWS + #include "ui/models/protocols/ikev2ConfigModel.h" +#endif +#include "ui/models/protocols/openvpnConfigModel.h" +#include "ui/models/protocols/shadowsocksConfigModel.h" +#include "ui/models/protocols/wireguardConfigModel.h" +#include "ui/models/protocols_model.h" #include "ui/models/servers_model.h" #define amnApp (static_cast(QCoreApplication::instance())) @@ -56,6 +64,9 @@ public: QQmlApplicationEngine *qmlEngine() const; private: + void initModels(); + void initControllers(); + QQmlApplicationEngine *m_engine {}; std::shared_ptr m_settings; std::shared_ptr m_configurator; @@ -69,6 +80,15 @@ private: QSharedPointer m_containersModel; QSharedPointer m_serversModel; QScopedPointer m_languageModel; + QScopedPointer m_protocolsModel; + + QScopedPointer m_openVpnConfigModel; + QScopedPointer m_shadowSocksConfigModel; + QScopedPointer m_cloakConfigModel; + QScopedPointer m_wireguardConfigModel; +#ifdef Q_OS_WINDOWS + QScopedPointer m_ikev2ConfigModel; +#endif QSharedPointer m_vpnConnection; diff --git a/client/protocols/protocols_defs.h b/client/protocols/protocols_defs.h index 1f890f4c..73c2abdf 100644 --- a/client/protocols/protocols_defs.h +++ b/client/protocols/protocols_defs.h @@ -1,229 +1,223 @@ #ifndef PROTOCOLS_DEFS_H #define PROTOCOLS_DEFS_H -#include #include +#include #include -namespace amnezia { -namespace config_key { - -// Json config strings -constexpr char hostName[] = "hostName"; -constexpr char userName[] = "userName"; -constexpr char password[] = "password"; -constexpr char port[] = "port"; -constexpr char local_port[] = "local_port"; - -constexpr char dns1[] = "dns1"; -constexpr char dns2[] = "dns2"; - - -constexpr char description[] = "description"; -constexpr char cert[] = "cert"; -constexpr char config[] = "config"; - - -constexpr char containers[] = "containers"; -constexpr char container[] = "container"; -constexpr char defaultContainer[] = "defaultContainer"; - -constexpr char vpnproto[] = "protocol"; -constexpr char protocols[] = "protocols"; - -constexpr char remote[] = "remote"; -constexpr char transport_proto[] = "transport_proto"; -constexpr char cipher[] = "cipher"; -constexpr char hash[] = "hash"; -constexpr char ncp_disable[] = "ncp_disable"; -constexpr char tls_auth[] = "tls_auth"; - -constexpr char client_priv_key[] = "client_priv_key"; -constexpr char client_pub_key[] = "client_pub_key"; -constexpr char server_priv_key[] = "server_priv_key"; -constexpr char server_pub_key[] = "server_pub_key"; -constexpr char psk_key[] = "psk_key"; - -constexpr char client_ip[] = "client_ip"; // internal ip address - -constexpr char site[] = "site"; -constexpr char block_outside_dns[] = "block_outside_dns"; - -constexpr char subnet_address[] = "subnet_address"; -constexpr char subnet_mask[] = "subnet_mask"; -constexpr char subnet_cidr[] = "subnet_cidr"; - -constexpr char additional_client_config[] = "additional_client_config"; -constexpr char additional_server_config[] = "additional_server_config"; - -// proto config keys -constexpr char last_config[] = "last_config"; - -constexpr char isThirdPartyConfig[] = "isThirdPartyConfig"; - -constexpr char openvpn[] = "openvpn"; -constexpr char wireguard[] = "wireguard"; - -} - -namespace protocols { - -namespace dns { -constexpr char amneziaDnsIp[] = "172.29.172.254"; -} - -namespace openvpn { -constexpr char defaultSubnetAddress[] = "10.8.0.0"; -constexpr char defaultSubnetMask[] = "255.255.255.0"; -constexpr char defaultSubnetCidr[] = "24"; - -constexpr char serverConfigPath[] = "/opt/amnezia/openvpn/server.conf"; -constexpr char caCertPath[] = "/opt/amnezia/openvpn/pki/ca.crt"; -constexpr char clientCertPath[] = "/opt/amnezia/openvpn/pki/issued"; -constexpr char taKeyPath[] = "/opt/amnezia/openvpn/ta.key"; -constexpr char clientsDirPath[] = "/opt/amnezia/openvpn/clients"; -constexpr char defaultPort[] = "1194"; -constexpr char defaultTransportProto[] = "udp"; -constexpr char defaultCipher[] = "AES-256-GCM"; -constexpr char defaultHash[] = "SHA512"; -constexpr bool defaultBlockOutsideDns = true; -constexpr bool defaultNcpDisable = false; -constexpr bool defaultTlsAuth = true; -constexpr char ncpDisableString[] = "ncp-disable"; -constexpr char tlsAuthString[] = "tls-auth /opt/amnezia/openvpn/ta.key 0"; - -constexpr char defaultAdditionalClientConfig[] = ""; -constexpr char defaultAdditionalServerConfig[] = ""; -} - -namespace shadowsocks { -constexpr char ssKeyPath[] = "/opt/amnezia/shadowsocks/shadowsocks.key"; -constexpr char defaultPort[] = "6789"; -constexpr char defaultLocalProxyPort[] = "8585"; -constexpr char defaultCipher[] = "chacha20-ietf-poly1305"; -} - -namespace cloak { -constexpr char ckPublicKeyPath[] = "/opt/amnezia/cloak/cloak_public.key"; -constexpr char ckBypassUidKeyPath[] = "/opt/amnezia/cloak/cloak_bypass_uid.key"; -constexpr char ckAdminKeyPath[] = "/opt/amnezia/cloak/cloak_admin_uid.key"; -constexpr char defaultPort[] = "443"; -constexpr char defaultRedirSite[] = "tile.openstreetmap.org"; -constexpr char defaultCipher[] = "chacha20-poly1305"; - -} - -namespace wireguard { -constexpr char defaultSubnetAddress[] = "10.8.1.0"; -constexpr char defaultSubnetMask[] = "255.255.255.0"; -constexpr char defaultSubnetCidr[] = "24"; - -constexpr char defaultPort[] = "51820"; -constexpr char serverConfigPath[] = "/opt/amnezia/wireguard/wg0.conf"; -constexpr char serverPublicKeyPath[] = "/opt/amnezia/wireguard/wireguard_server_public_key.key"; -constexpr char serverPskKeyPath[] = "/opt/amnezia/wireguard/wireguard_psk.key"; - -} - -namespace sftp { -constexpr char defaultUserName[] = "sftp_user"; - -} // namespace sftp - -} // namespace protocols - -namespace ProtocolEnumNS { -Q_NAMESPACE - -enum TransportProto { - Udp, - Tcp -}; -Q_ENUM_NS(TransportProto) - -enum Proto { - Any = 0, - OpenVpn, - ShadowSocks, - Cloak, - WireGuard, - Ikev2, - L2tp, - - // non-vpn - TorWebSite, - Dns, - FileShare, - Sftp -}; -Q_ENUM_NS(Proto) - -enum ServiceType { - None = 0, - Vpn, - Other -}; -Q_ENUM_NS(ServiceType) -} // namespace ProtocolEnumNS - -using namespace ProtocolEnumNS; - -class ProtocolProps : public QObject +namespace amnezia { - Q_OBJECT + namespace config_key + { -public: - Q_INVOKABLE static QList allProtocols(); + // Json config strings + constexpr char hostName[] = "hostName"; + constexpr char userName[] = "userName"; + constexpr char password[] = "password"; + constexpr char port[] = "port"; + constexpr char local_port[] = "local_port"; - // spelling may differ for various protocols - TCP for OpenVPN, tcp for others - Q_INVOKABLE static TransportProto transportProtoFromString(QString p); - Q_INVOKABLE static QString transportProtoToString(TransportProto proto, Proto p = Proto::Any); + constexpr char dns1[] = "dns1"; + constexpr char dns2[] = "dns2"; - Q_INVOKABLE static Proto protoFromString(QString p); - Q_INVOKABLE static QString protoToString(Proto p); + constexpr char description[] = "description"; + constexpr char cert[] = "cert"; + constexpr char config[] = "config"; - Q_INVOKABLE static QMap protocolHumanNames(); - Q_INVOKABLE static QMap protocolDescriptions(); + constexpr char containers[] = "containers"; + constexpr char container[] = "container"; + constexpr char defaultContainer[] = "defaultContainer"; - Q_INVOKABLE static ServiceType protocolService(Proto p); + constexpr char vpnproto[] = "protocol"; + constexpr char protocols[] = "protocols"; - Q_INVOKABLE static int defaultPort(Proto p); - Q_INVOKABLE static bool defaultPortChangeable(Proto p); + constexpr char remote[] = "remote"; + constexpr char transport_proto[] = "transport_proto"; + constexpr char cipher[] = "cipher"; + constexpr char hash[] = "hash"; + constexpr char ncp_disable[] = "ncp_disable"; + constexpr char tls_auth[] = "tls_auth"; - Q_INVOKABLE static TransportProto defaultTransportProto(Proto p); - Q_INVOKABLE static bool defaultTransportProtoChangeable(Proto p); + constexpr char client_priv_key[] = "client_priv_key"; + constexpr char client_pub_key[] = "client_pub_key"; + constexpr char server_priv_key[] = "server_priv_key"; + constexpr char server_pub_key[] = "server_pub_key"; + constexpr char psk_key[] = "psk_key"; + constexpr char client_ip[] = "client_ip"; // internal ip address - Q_INVOKABLE static QString key_proto_config_data(Proto p); - Q_INVOKABLE static QString key_proto_config_path(Proto p); + constexpr char site[] = "site"; + constexpr char block_outside_dns[] = "block_outside_dns"; -}; + constexpr char subnet_address[] = "subnet_address"; + constexpr char subnet_mask[] = "subnet_mask"; + constexpr char subnet_cidr[] = "subnet_cidr"; -static void declareQmlProtocolEnum() { - qmlRegisterUncreatableMetaObject( - ProtocolEnumNS::staticMetaObject, - "ProtocolEnum", - 1, 0, - "ProtocolEnum", - "Error: only enums" - ); + constexpr char additional_client_config[] = "additional_client_config"; + constexpr char additional_server_config[] = "additional_server_config"; - qmlRegisterUncreatableMetaObject( - ProtocolEnumNS::staticMetaObject, - "ProtocolEnum", - 1, 0, - "TransportProto", - "Error: only enums" - ); + // proto config keys + constexpr char last_config[] = "last_config"; - qmlRegisterUncreatableMetaObject( - ProtocolEnumNS::staticMetaObject, - "ProtocolEnum", - 1, 0, - "ServiceType", - "Error: only enums" - ); -} + constexpr char isThirdPartyConfig[] = "isThirdPartyConfig"; + + constexpr char openvpn[] = "openvpn"; + constexpr char wireguard[] = "wireguard"; + constexpr char shadowsocks[] = "shadowsocks"; + constexpr char cloak[] = "cloak"; + + } + + namespace protocols + { + + namespace dns + { + constexpr char amneziaDnsIp[] = "172.29.172.254"; + } + + namespace openvpn + { + constexpr char defaultSubnetAddress[] = "10.8.0.0"; + constexpr char defaultSubnetMask[] = "255.255.255.0"; + constexpr char defaultSubnetCidr[] = "24"; + + constexpr char serverConfigPath[] = "/opt/amnezia/openvpn/server.conf"; + constexpr char caCertPath[] = "/opt/amnezia/openvpn/pki/ca.crt"; + constexpr char clientCertPath[] = "/opt/amnezia/openvpn/pki/issued"; + constexpr char taKeyPath[] = "/opt/amnezia/openvpn/ta.key"; + constexpr char clientsDirPath[] = "/opt/amnezia/openvpn/clients"; + constexpr char defaultPort[] = "1194"; + constexpr char defaultTransportProto[] = "udp"; + constexpr char defaultCipher[] = "AES-256-GCM"; + constexpr char defaultHash[] = "SHA512"; + constexpr bool defaultBlockOutsideDns = true; + constexpr bool defaultNcpDisable = false; + constexpr bool defaultTlsAuth = true; + constexpr char ncpDisableString[] = "ncp-disable"; + constexpr char tlsAuthString[] = "tls-auth /opt/amnezia/openvpn/ta.key 0"; + + constexpr char defaultAdditionalClientConfig[] = ""; + constexpr char defaultAdditionalServerConfig[] = ""; + } + + namespace shadowsocks + { + constexpr char ssKeyPath[] = "/opt/amnezia/shadowsocks/shadowsocks.key"; + constexpr char defaultPort[] = "6789"; + constexpr char defaultLocalProxyPort[] = "8585"; + constexpr char defaultCipher[] = "chacha20-ietf-poly1305"; + } + + namespace cloak + { + constexpr char ckPublicKeyPath[] = "/opt/amnezia/cloak/cloak_public.key"; + constexpr char ckBypassUidKeyPath[] = "/opt/amnezia/cloak/cloak_bypass_uid.key"; + constexpr char ckAdminKeyPath[] = "/opt/amnezia/cloak/cloak_admin_uid.key"; + constexpr char defaultPort[] = "443"; + constexpr char defaultRedirSite[] = "tile.openstreetmap.org"; + constexpr char defaultCipher[] = "chacha20-poly1305"; + + } + + namespace wireguard + { + constexpr char defaultSubnetAddress[] = "10.8.1.0"; + constexpr char defaultSubnetMask[] = "255.255.255.0"; + constexpr char defaultSubnetCidr[] = "24"; + + constexpr char defaultPort[] = "51820"; + constexpr char serverConfigPath[] = "/opt/amnezia/wireguard/wg0.conf"; + constexpr char serverPublicKeyPath[] = "/opt/amnezia/wireguard/wireguard_server_public_key.key"; + constexpr char serverPskKeyPath[] = "/opt/amnezia/wireguard/wireguard_psk.key"; + + } + + namespace sftp + { + constexpr char defaultUserName[] = "sftp_user"; + + } // namespace sftp + + } // namespace protocols + + namespace ProtocolEnumNS + { + Q_NAMESPACE + + enum TransportProto { + Udp, + Tcp + }; + Q_ENUM_NS(TransportProto) + + enum Proto { + Any = 0, + OpenVpn, + ShadowSocks, + Cloak, + WireGuard, + Ikev2, + L2tp, + + // non-vpn + TorWebSite, + Dns, + FileShare, + Sftp + }; + Q_ENUM_NS(Proto) + + enum ServiceType { + None = 0, + Vpn, + Other + }; + Q_ENUM_NS(ServiceType) + } // namespace ProtocolEnumNS + + using namespace ProtocolEnumNS; + + class ProtocolProps : public QObject + { + Q_OBJECT + + public: + Q_INVOKABLE static QList allProtocols(); + + // spelling may differ for various protocols - TCP for OpenVPN, tcp for others + Q_INVOKABLE static TransportProto transportProtoFromString(QString p); + Q_INVOKABLE static QString transportProtoToString(TransportProto proto, Proto p = Proto::Any); + + Q_INVOKABLE static Proto protoFromString(QString p); + Q_INVOKABLE static QString protoToString(Proto p); + + Q_INVOKABLE static QMap protocolHumanNames(); + Q_INVOKABLE static QMap protocolDescriptions(); + + Q_INVOKABLE static ServiceType protocolService(Proto p); + + Q_INVOKABLE static int defaultPort(Proto p); + Q_INVOKABLE static bool defaultPortChangeable(Proto p); + + Q_INVOKABLE static TransportProto defaultTransportProto(Proto p); + Q_INVOKABLE static bool defaultTransportProtoChangeable(Proto p); + + Q_INVOKABLE static QString key_proto_config_data(Proto p); + Q_INVOKABLE static QString key_proto_config_path(Proto p); + }; + + static void declareQmlProtocolEnum() + { + qmlRegisterUncreatableMetaObject(ProtocolEnumNS::staticMetaObject, "ProtocolEnum", 1, 0, "ProtocolEnum", + "Error: only enums"); + + qmlRegisterUncreatableMetaObject(ProtocolEnumNS::staticMetaObject, "ProtocolEnum", 1, 0, "TransportProto", + "Error: only enums"); + + qmlRegisterUncreatableMetaObject(ProtocolEnumNS::staticMetaObject, "ProtocolEnum", 1, 0, "ServiceType", + "Error: only enums"); + } } // namespace amnezia diff --git a/client/resources.qrc b/client/resources.qrc index b7bdd463..dd75555f 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -250,7 +250,6 @@ ui/qml/Pages2/PageDeinstalling.qml ui/qml/Controls2/BackButtonType.qml ui/qml/Pages2/PageSettingsServerProtocol.qml - ui/qml/Components/Protocols/OpenVpnSettings.qml ui/qml/Components/TransportProtoSelector.qml ui/qml/Controls2/ListViewType.qml images/controls/radio-button.svg @@ -271,5 +270,8 @@ ui/qml/Filters/ContainersModelFilters.qml ui/qml/Components/SelectLanguageDrawer.qml ui/qml/Controls2/BusyIndicatorType.qml + ui/qml/Pages2/PageProtocolOpenVpnSettings.qml + ui/qml/Pages2/PageProtocolShadowSocksSettings.qml + ui/qml/Pages2/PageProtocolCloakSettings.qml diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 1809e082..6fc5f4e9 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -69,7 +69,7 @@ void InstallController::installServer(DockerContainer container, QJsonObject &co m_serversModel->addServer(server); m_serversModel->setDefaultServerIndex(m_serversModel->getServersCount() - 1); - emit installServerFinished(isInstalledContainerFound); + emit installServerFinished(false); // todo incorrect notification about found containers return; } @@ -108,7 +108,7 @@ void InstallController::installContainer(DockerContainer container, QJsonObject } } - emit installContainerFinished(isInstalledContainerFound); + emit installContainerFinished(false); // todo incorrect notification about found containers return; } @@ -162,6 +162,29 @@ void InstallController::scanServerForInstalledContainers() emit installationErrorOccurred(errorString(errorCode)); } +void InstallController::updateContainer(QJsonObject config) +{ + int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + ServerCredentials serverCredentials = + qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); + + const DockerContainer container = ContainerProps::containerFromString(config.value(config_key::container).toString()); + auto modelIndex = m_containersModel->index(container); + QJsonObject oldContainerConfig = + qvariant_cast(m_containersModel->data(modelIndex, ContainersModel::Roles::ConfigRole)); + + ServerController serverController(m_settings); + + auto errorCode = serverController.updateContainer(serverCredentials, container, oldContainerConfig, config); + if (errorCode == ErrorCode::NoError) { + m_containersModel->setData(modelIndex, config, ContainersModel::Roles::ConfigRole); + emit updateContainerFinished(); + return; + } + + emit installationErrorOccurred(errorString(errorCode)); +} + QRegularExpression InstallController::ipAddressPortRegExp() { return Utils::ipAddressPortRegExp(); diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index 6b01e102..75f4fdef 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -24,12 +24,16 @@ public slots: void scanServerForInstalledContainers(); + void updateContainer(QJsonObject config); + QRegularExpression ipAddressPortRegExp(); signals: void installContainerFinished(bool isInstalledContainerFound); void installServerFinished(bool isInstalledContainerFound); + void updateContainerFinished(); + void scanServerFinished(bool isInstalledContainerFound); void installationErrorOccurred(QString errorMessage); diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index f67da275..468068b8 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -36,7 +36,13 @@ namespace PageLoader PageSetupWizardInstalling, PageSetupWizardConfigSource, PageSetupWizardTextKey, - PageSetupWizardViewConfig + PageSetupWizardViewConfig, + + PageProtocolOpenVpnSettings, + PageProtocolShadowSocksSettings, + PageProtocolCloakSettings, + PageProtocolWireGuardSettings, + PageProtocolIKev2Settings }; Q_ENUM_NS(PageEnum) diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index ecacc184..7b6ffaee 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -29,6 +29,11 @@ bool ContainersModel::setData(const QModelIndex &index, const QVariant &value, i case ConfigRole: { m_settings->setContainerConfig(m_currentlyProcessedServerIndex, container, value.toJsonObject()); m_containers = m_settings->containers(m_currentlyProcessedServerIndex); + if (m_defaultContainerIndex != DockerContainer::None) { + break; + } else if (ContainerProps::containerService(container) == ServiceType::Other) { + break; + } } case ServiceTypeRole: // return ContainerProps::containerService(container); @@ -108,6 +113,11 @@ int ContainersModel::getCurrentlyProcessedContainerIndex() return m_currentlyProcessedContainerIndex; } +QString ContainersModel::getCurrentlyProcessedContainerName() +{ + return ContainerProps::containerHumanNames().value(static_cast(m_currentlyProcessedContainerIndex)); +} + void ContainersModel::removeAllContainers() { @@ -116,14 +126,39 @@ void ContainersModel::removeAllContainers() if (errorCode == ErrorCode::NoError) { beginResetModel(); + m_settings->setContainers(m_currentlyProcessedServerIndex, {}); - m_settings->setDefaultContainer(m_currentlyProcessedServerIndex, DockerContainer::None); + m_containers = m_settings->containers(m_currentlyProcessedServerIndex); + + setData(index(DockerContainer::None, 0), true, IsDefaultRole); endResetModel(); } // todo process errors } +void ContainersModel::removeCurrentlyProcessedContainer() +{ + ServerController serverController(m_settings); + auto credentials = m_settings->serverCredentials(m_currentlyProcessedServerIndex); + auto dockerContainer = static_cast(m_currentlyProcessedContainerIndex); + + ErrorCode e = serverController.removeContainer(credentials, dockerContainer); + + beginResetModel(); // todo change to begin remove rows? + m_settings->removeContainerConfig(m_currentlyProcessedServerIndex, dockerContainer); + m_containers = m_settings->containers(m_currentlyProcessedServerIndex); + + if (m_defaultContainerIndex == m_currentlyProcessedContainerIndex) { + if (m_containers.isEmpty()) { + setData(index(DockerContainer::None, 0), true, IsDefaultRole); + } else { + setData(index(m_containers.begin().key(), 0), true, IsDefaultRole); + } + } + endResetModel(); +} + void ContainersModel::clearCachedProfiles() { const auto &containers = m_settings->containers(m_currentlyProcessedServerIndex); @@ -150,6 +185,7 @@ QHash ContainersModel::roleNames() const roles[DescRole] = "description"; roles[ServiceTypeRole] = "serviceType"; roles[DockerContainerRole] = "dockerContainer"; + roles[ConfigRole] = "config"; roles[IsEasySetupContainerRole] = "isEasySetupContainer"; roles[EasySetupHeaderRole] = "easySetupHeader"; diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index 7331ef22..b79b058a 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -45,10 +45,14 @@ public slots: QString getDefaultContainerName(); void setCurrentlyProcessedServerIndex(const int index); + void setCurrentlyProcessedContainerIndex(int index); int getCurrentlyProcessedContainerIndex(); + QString getCurrentlyProcessedContainerName(); + void removeAllContainers(); + void removeCurrentlyProcessedContainer(); void clearCachedProfiles(); bool isAmneziaDnsContainerInstalled(); diff --git a/client/ui/models/languageModel.cpp b/client/ui/models/languageModel.cpp index e95b2ccf..76eeb37d 100644 --- a/client/ui/models/languageModel.cpp +++ b/client/ui/models/languageModel.cpp @@ -22,14 +22,8 @@ QVariant LanguageModel::data(const QModelIndex &index, int role) const } switch (role) { - case NameRole: { - return m_availableLanguages[index.row()].name; - break; - } - case IndexRole: { - return static_cast(m_availableLanguages[index.row()].index); - break; - } + case NameRole: return m_availableLanguages[index.row()].name; + case IndexRole: return static_cast(m_availableLanguages[index.row()].index); } return QVariant(); } diff --git a/client/ui/models/protocols/cloakConfigModel.cpp b/client/ui/models/protocols/cloakConfigModel.cpp new file mode 100644 index 00000000..203f08b5 --- /dev/null +++ b/client/ui/models/protocols/cloakConfigModel.cpp @@ -0,0 +1,81 @@ +#include "cloakConfigModel.h" + +#include "protocols/protocols_defs.h" + +CloakConfigModel::CloakConfigModel(QObject *parent) : QAbstractListModel(parent) +{ +} + +int CloakConfigModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 1; +} + +bool CloakConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { + return false; + } + + switch (role) { + case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break; + case Roles::CipherRole: m_protocolConfig.insert(config_key::cipher, value.toString()); break; + case Roles::SiteRole: m_protocolConfig.insert(config_key::site, value.toString()); break; + } + + emit dataChanged(index, index, QList { role }); + return true; +} + +QVariant CloakConfigModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { + return false; + } + + switch (role) { + case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(protocols::cloak::defaultPort); + case Roles::CipherRole: return m_protocolConfig.value(config_key::cipher).toString(protocols::cloak::defaultCipher); + case Roles::SiteRole: return m_protocolConfig.value(config_key::site).toString(protocols::cloak::defaultRedirSite); + } + + return QVariant(); +} + +void CloakConfigModel::updateModel(const QJsonObject &config) +{ + beginResetModel(); + m_container = ContainerProps::containerFromString(config.value(config_key::container).toString()); + + m_fullConfig = config; + QJsonObject protocolConfig = config.value(config_key::cloak).toObject(); + + m_protocolConfig.insert(config_key::cipher, + protocolConfig.value(config_key::cipher).toString(protocols::cloak::defaultCipher)); + + m_protocolConfig.insert(config_key::port, + protocolConfig.value(config_key::port).toString(protocols::cloak::defaultPort)); + + m_protocolConfig.insert(config_key::site, + protocolConfig.value(config_key::site).toString(protocols::cloak::defaultRedirSite)); + + endResetModel(); +} + +QJsonObject CloakConfigModel::getConfig() +{ + m_fullConfig.insert(config_key::cloak, m_protocolConfig); + return m_fullConfig; +} + +QHash CloakConfigModel::roleNames() const +{ + QHash roles; + + roles[PortRole] = "port"; + roles[CipherRole] = "cipher"; + roles[SiteRole] = "site"; + + return roles; +} diff --git a/client/ui/models/protocols/cloakConfigModel.h b/client/ui/models/protocols/cloakConfigModel.h new file mode 100644 index 00000000..31ff8c53 --- /dev/null +++ b/client/ui/models/protocols/cloakConfigModel.h @@ -0,0 +1,40 @@ +#ifndef CLOAKCONFIGMODEL_H +#define CLOAKCONFIGMODEL_H + +#include +#include + +#include "containers/containers_defs.h" + +class CloakConfigModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + PortRole = Qt::UserRole + 1, + CipherRole, + SiteRole + }; + + explicit CloakConfigModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +public slots: + void updateModel(const QJsonObject &config); + QJsonObject getConfig(); + +protected: + QHash roleNames() const override; + +private: + DockerContainer m_container; + QJsonObject m_protocolConfig; + QJsonObject m_fullConfig; +}; + +#endif // CLOAKCONFIGMODEL_H diff --git a/client/ui/models/protocols/ikev2ConfigModel.cpp b/client/ui/models/protocols/ikev2ConfigModel.cpp new file mode 100644 index 00000000..f22b965c --- /dev/null +++ b/client/ui/models/protocols/ikev2ConfigModel.cpp @@ -0,0 +1,76 @@ +#include "ikev2ConfigModel.h".h " + +#include "protocols/protocols_defs.h" + +Ikev2ConfigModel::Ikev2ConfigModel(QObject *parent) : QAbstractListModel(parent) +{ +} + +int Ikev2ConfigModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 1; +} + +bool Ikev2ConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { + return false; + } + + switch (role) { + case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break; + case Roles::CipherRole: m_protocolConfig.insert(config_key::cipher, value.toString()); break; + } + + emit dataChanged(index, index, QList { role }); + return true; +} + +QVariant Ikev2ConfigModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { + return false; + } + + switch (role) { + case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort); + case Roles::CipherRole: + return m_protocolConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher); + } + + return QVariant(); +} + +void Ikev2ConfigModel::updateModel(const QJsonObject &config) +{ + beginResetModel(); + m_container = ContainerProps::containerFromString(config.value(config_key::container).toString()); + + m_fullConfig = config; + QJsonObject protocolConfig = config.value(config_key::shadowsocks).toObject(); + + m_protocolConfig.insert(config_key::cipher, + protocolConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher)); + + m_protocolConfig.insert(config_key::port, + protocolConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort)); + + endResetModel(); +} + +QJsonObject Ikev2ConfigModel::getConfig() +{ + m_fullConfig.insert(config_key::shadowsocks, m_protocolConfig); + return m_fullConfig; +} + +QHash Ikev2ConfigModel::roleNames() const +{ + QHash roles; + + roles[PortRole] = "port"; + roles[CipherRole] = "cipher"; + + return roles; +} diff --git a/client/ui/models/protocols/ikev2ConfigModel.h b/client/ui/models/protocols/ikev2ConfigModel.h new file mode 100644 index 00000000..e005f6a4 --- /dev/null +++ b/client/ui/models/protocols/ikev2ConfigModel.h @@ -0,0 +1,39 @@ +#ifndef IKEV2CONFIGMODEL_H +#define IKEV2CONFIGMODEL_H + +#include +#include + +#include "containers/containers_defs.h" + +class Ikev2ConfigModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + PortRole = Qt::UserRole + 1, + CipherRole + }; + + explicit Ikev2ConfigModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +public slots: + void updateModel(const QJsonObject &config); + QJsonObject getConfig(); + +protected: + QHash roleNames() const override; + +private: + DockerContainer m_container; + QJsonObject m_protocolConfig; + QJsonObject m_fullConfig; +}; + +#endif // IKEV2CONFIGMODEL_H diff --git a/client/ui/models/protocols/openvpnConfigModel.cpp b/client/ui/models/protocols/openvpnConfigModel.cpp new file mode 100644 index 00000000..1b73c987 --- /dev/null +++ b/client/ui/models/protocols/openvpnConfigModel.cpp @@ -0,0 +1,152 @@ +#include "openvpnConfigModel.h" + +#include "protocols/protocols_defs.h" + +OpenVpnConfigModel::OpenVpnConfigModel(QObject *parent) : QAbstractListModel(parent) +{ +} + +int OpenVpnConfigModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 1; +} + +bool OpenVpnConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { + return false; + } + + switch (role) { + case Roles::SubnetAddressRole: + m_protocolConfig.insert(amnezia::config_key::subnet_address, value.toString()); + break; + case Roles::TransportProtoRole: m_protocolConfig.insert(config_key::transport_proto, value.toString()); break; + case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break; + case Roles::AutoNegotiateEncryprionRole: m_protocolConfig.insert(config_key::ncp_disable, !value.toBool()); break; + case Roles::HashRole: m_protocolConfig.insert(config_key::hash, value.toString()); break; + case Roles::CipherRole: m_protocolConfig.insert(config_key::cipher, value.toString()); break; + case Roles::TlsAuthRole: m_protocolConfig.insert(config_key::tls_auth, value.toBool()); break; + case Roles::BlockDnsRole: m_protocolConfig.insert(config_key::block_outside_dns, value.toBool()); break; + case Roles::AdditionalClientCommandsRole: + m_protocolConfig.insert(config_key::additional_client_config, value.toString()); + break; + case Roles::AdditionalServerCommandsRole: + m_protocolConfig.insert(config_key::additional_server_config, value.toString()); + break; + } + + emit dataChanged(index, index, QList { role }); + return true; +} + +QVariant OpenVpnConfigModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { + return false; + } + + switch (role) { + case Roles::SubnetAddressRole: + return m_protocolConfig.value(amnezia::config_key::subnet_address) + .toString(amnezia::protocols::openvpn::defaultSubnetAddress); + case Roles::TransportProtoRole: + return m_protocolConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto); + case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(protocols::openvpn::defaultPort); + case Roles::AutoNegotiateEncryprionRole: + return !m_protocolConfig.value(config_key::ncp_disable).toBool(protocols::openvpn::defaultNcpDisable); + case Roles::HashRole: return m_protocolConfig.value(config_key::hash).toString(protocols::openvpn::defaultHash); + case Roles::CipherRole: + return m_protocolConfig.value(config_key::cipher).toString(protocols::openvpn::defaultCipher); + case Roles::TlsAuthRole: + return m_protocolConfig.value(config_key::tls_auth).toBool(protocols::openvpn::defaultTlsAuth); + case Roles::BlockDnsRole: + return m_protocolConfig.value(config_key::block_outside_dns).toBool(protocols::openvpn::defaultBlockOutsideDns); + case Roles::AdditionalClientCommandsRole: + return m_protocolConfig.value(config_key::additional_client_config) + .toString(protocols::openvpn::defaultAdditionalClientConfig); + case Roles::AdditionalServerCommandsRole: + return m_protocolConfig.value(config_key::additional_server_config) + .toString(protocols::openvpn::defaultAdditionalServerConfig); + case Roles::IsPortEditable: return m_container == DockerContainer::OpenVpn ? true : false; + case Roles::IsTransportProtoEditable: return m_container == DockerContainer::OpenVpn ? true : false; + case Roles::HasRemoveButton: return m_container == DockerContainer::OpenVpn ? true : false; + } + return QVariant(); +} + +void OpenVpnConfigModel::updateModel(const QJsonObject &config) +{ + beginResetModel(); + m_container = + ContainerProps::containerFromString(config.value(config_key::container).toString()); // todo maybe unused + + m_fullConfig = config; + QJsonObject protocolConfig = config.value(config_key::openvpn).toObject(); + + m_protocolConfig.insert(config_key::subnet_address, + protocolConfig.value(amnezia::config_key::subnet_address) + .toString(amnezia::protocols::openvpn::defaultSubnetAddress)); + + QString transportProto; + if (m_container == DockerContainer::OpenVpn) { + transportProto = + protocolConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto); + } else { + transportProto = "tcp"; + } + + m_protocolConfig.insert(config_key::transport_proto, transportProto); + + m_protocolConfig.insert(config_key::ncp_disable, + protocolConfig.value(config_key::ncp_disable).toBool(protocols::openvpn::defaultNcpDisable)); + m_protocolConfig.insert(config_key::cipher, + protocolConfig.value(config_key::cipher).toString(protocols::openvpn::defaultCipher)); + m_protocolConfig.insert(config_key::hash, + protocolConfig.value(config_key::hash).toString(protocols::openvpn::defaultHash)); + m_protocolConfig.insert(config_key::block_outside_dns, + protocolConfig.value(config_key::tls_auth).toBool(protocols::openvpn::defaultTlsAuth)); + m_protocolConfig.insert(config_key::port, + protocolConfig.value(config_key::port).toString(protocols::openvpn::defaultPort)); + m_protocolConfig.insert( + config_key::tls_auth, + protocolConfig.value(config_key::block_outside_dns).toBool(protocols::openvpn::defaultBlockOutsideDns)); + m_protocolConfig.insert(config_key::additional_client_config, + protocolConfig.value(config_key::additional_client_config) + .toString(protocols::openvpn::defaultAdditionalClientConfig)); + m_protocolConfig.insert(config_key::additional_server_config, + protocolConfig.value(config_key::additional_server_config) + .toString(protocols::openvpn::defaultAdditionalServerConfig)); + + endResetModel(); +} + +QJsonObject OpenVpnConfigModel::getConfig() +{ + m_fullConfig.insert(config_key::openvpn, m_protocolConfig); + return m_fullConfig; +} + +QHash OpenVpnConfigModel::roleNames() const +{ + QHash roles; + + roles[SubnetAddressRole] = "subnetAddress"; + roles[TransportProtoRole] = "transportProto"; + roles[PortRole] = "port"; + roles[AutoNegotiateEncryprionRole] = "autoNegotiateEncryprion"; + roles[HashRole] = "hash"; + roles[CipherRole] = "cipher"; + roles[TlsAuthRole] = "tlsAuth"; + roles[BlockDnsRole] = "blockDns"; + roles[AdditionalClientCommandsRole] = "additionalClientCommands"; + roles[AdditionalServerCommandsRole] = "additionalServerCommands"; + + roles[IsPortEditable] = "isPortEditable"; + roles[IsTransportProtoEditable] = "isTransportProtoEditable"; + + roles[HasRemoveButton] = "hasRemoveButton"; + + return roles; +} diff --git a/client/ui/models/protocols/openvpnConfigModel.h b/client/ui/models/protocols/openvpnConfigModel.h new file mode 100644 index 00000000..0357700c --- /dev/null +++ b/client/ui/models/protocols/openvpnConfigModel.h @@ -0,0 +1,52 @@ +#ifndef OPENVPNCONFIGMODEL_H +#define OPENVPNCONFIGMODEL_H + +#include +#include + +#include "containers/containers_defs.h" + +class OpenVpnConfigModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + SubnetAddressRole = Qt::UserRole + 1, + TransportProtoRole, + PortRole, + AutoNegotiateEncryprionRole, + HashRole, + CipherRole, + TlsAuthRole, + BlockDnsRole, + AdditionalClientCommandsRole, + AdditionalServerCommandsRole, + + IsPortEditable, + IsTransportProtoEditable, + + HasRemoveButton + }; + + explicit OpenVpnConfigModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +public slots: + void updateModel(const QJsonObject &config); + QJsonObject getConfig(); + +protected: + QHash roleNames() const override; + +private: + DockerContainer m_container; + QJsonObject m_protocolConfig; + QJsonObject m_fullConfig; +}; + +#endif // OPENVPNCONFIGMODEL_H diff --git a/client/ui/models/protocols/shadowsocksConfigModel.cpp b/client/ui/models/protocols/shadowsocksConfigModel.cpp new file mode 100644 index 00000000..60c8feee --- /dev/null +++ b/client/ui/models/protocols/shadowsocksConfigModel.cpp @@ -0,0 +1,76 @@ +#include "shadowsocksConfigModel.h" + +#include "protocols/protocols_defs.h" + +ShadowSocksConfigModel::ShadowSocksConfigModel(QObject *parent) : QAbstractListModel(parent) +{ +} + +int ShadowSocksConfigModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 1; +} + +bool ShadowSocksConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { + return false; + } + + switch (role) { + case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break; + case Roles::CipherRole: m_protocolConfig.insert(config_key::cipher, value.toString()); break; + } + + emit dataChanged(index, index, QList { role }); + return true; +} + +QVariant ShadowSocksConfigModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { + return false; + } + + switch (role) { + case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort); + case Roles::CipherRole: + return m_protocolConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher); + } + + return QVariant(); +} + +void ShadowSocksConfigModel::updateModel(const QJsonObject &config) +{ + beginResetModel(); + m_container = ContainerProps::containerFromString(config.value(config_key::container).toString()); + + m_fullConfig = config; + QJsonObject protocolConfig = config.value(config_key::shadowsocks).toObject(); + + m_protocolConfig.insert(config_key::cipher, + protocolConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher)); + + m_protocolConfig.insert(config_key::port, + protocolConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort)); + + endResetModel(); +} + +QJsonObject ShadowSocksConfigModel::getConfig() +{ + m_fullConfig.insert(config_key::shadowsocks, m_protocolConfig); + return m_fullConfig; +} + +QHash ShadowSocksConfigModel::roleNames() const +{ + QHash roles; + + roles[PortRole] = "port"; + roles[CipherRole] = "cipher"; + + return roles; +} diff --git a/client/ui/models/protocols/shadowsocksConfigModel.h b/client/ui/models/protocols/shadowsocksConfigModel.h new file mode 100644 index 00000000..d8fa036b --- /dev/null +++ b/client/ui/models/protocols/shadowsocksConfigModel.h @@ -0,0 +1,39 @@ +#ifndef SHADOWSOCKSCONFIGMODEL_H +#define SHADOWSOCKSCONFIGMODEL_H + +#include +#include + +#include "containers/containers_defs.h" + +class ShadowSocksConfigModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + PortRole = Qt::UserRole + 1, + CipherRole + }; + + explicit ShadowSocksConfigModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +public slots: + void updateModel(const QJsonObject &config); + QJsonObject getConfig(); + +protected: + QHash roleNames() const override; + +private: + DockerContainer m_container; + QJsonObject m_protocolConfig; + QJsonObject m_fullConfig; +}; + +#endif // SHADOWSOCKSCONFIGMODEL_H diff --git a/client/ui/models/protocols/wireguardConfigModel.cpp b/client/ui/models/protocols/wireguardConfigModel.cpp new file mode 100644 index 00000000..15e89865 --- /dev/null +++ b/client/ui/models/protocols/wireguardConfigModel.cpp @@ -0,0 +1,70 @@ +#include "wireguardConfigModel.h" + +#include "protocols/protocols_defs.h" + +WireGuardConfigModel::WireGuardConfigModel(QObject *parent) : QAbstractListModel(parent) +{ +} + +int WireGuardConfigModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 1; +} + +bool WireGuardConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { + return false; + } + + switch (role) { + case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break; + case Roles::CipherRole: m_protocolConfig.insert(config_key::cipher, value.toString()); break; + } + + emit dataChanged(index, index, QList { role }); + return true; +} + +QVariant WireGuardConfigModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { + return false; + } + + switch (role) { + case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort); + case Roles::CipherRole: + return m_protocolConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher); + } + + return QVariant(); +} + +void WireGuardConfigModel::updateModel(const QJsonObject &config) +{ + beginResetModel(); + m_container = ContainerProps::containerFromString(config.value(config_key::container).toString()); + + m_fullConfig = config; + QJsonObject protocolConfig = config.value(config_key::wireguard).toObject(); + + endResetModel(); +} + +QJsonObject WireGuardConfigModel::getConfig() +{ + m_fullConfig.insert(config_key::wireguard, m_protocolConfig); + return m_fullConfig; +} + +QHash WireGuardConfigModel::roleNames() const +{ + QHash roles; + + roles[PortRole] = "port"; + roles[CipherRole] = "cipher"; + + return roles; +} diff --git a/client/ui/models/protocols/wireguardConfigModel.h b/client/ui/models/protocols/wireguardConfigModel.h new file mode 100644 index 00000000..1deeacaf --- /dev/null +++ b/client/ui/models/protocols/wireguardConfigModel.h @@ -0,0 +1,39 @@ +#ifndef WIREGUARDCONFIGMODEL_H +#define WIREGUARDCONFIGMODEL_H + +#include +#include + +#include "containers/containers_defs.h" + +class WireGuardConfigModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + PortRole = Qt::UserRole + 1, + CipherRole + }; + + explicit WireGuardConfigModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +public slots: + void updateModel(const QJsonObject &config); + QJsonObject getConfig(); + +protected: + QHash roleNames() const override; + +private: + DockerContainer m_container; + QJsonObject m_protocolConfig; + QJsonObject m_fullConfig; +}; + +#endif // WIREGUARDCONFIGMODEL_H diff --git a/client/ui/models/protocols_model.cpp b/client/ui/models/protocols_model.cpp index 7359bb36..ac271eb9 100644 --- a/client/ui/models/protocols_model.cpp +++ b/client/ui/models/protocols_model.cpp @@ -1,62 +1,73 @@ #include "protocols_model.h" -ProtocolsModel::ProtocolsModel(std::shared_ptr settings, QObject *parent) : - m_settings(settings), - QAbstractListModel(parent) +ProtocolsModel::ProtocolsModel(std::shared_ptr settings, QObject *parent) + : m_settings(settings), QAbstractListModel(parent) { - } int ProtocolsModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); - return ProtocolProps::allProtocols().size(); + return m_content.size(); } -QHash ProtocolsModel::roleNames() const { +QHash ProtocolsModel::roleNames() const +{ QHash roles; - roles[NameRole] = "name_role"; - roles[DescRole] = "desc_role"; - roles[ServiceTypeRole] = "service_type_role"; - roles[IsInstalledRole] = "is_installed_role"; + + roles[ProtocolNameRole] = "protocolName"; + roles[ProtocolPageRole] = "protocolPage"; + return roles; } QVariant ProtocolsModel::data(const QModelIndex &index, int role) const { - if (!index.isValid() || index.row() < 0 - || index.row() >= ProtocolProps::allProtocols().size()) { + if (!index.isValid() || index.row() < 0 || index.row() >= m_content.size()) { return QVariant(); } - Proto p = ProtocolProps::allProtocols().at(index.row()); - if (role == NameRole) { - return ProtocolProps::protocolHumanNames().value(p); + switch (role) { + case ProtocolNameRole: { + amnezia::Proto proto = ProtocolProps::protoFromString(m_content.keys().at(index.row())); + return ProtocolProps::protocolHumanNames().value(proto); } - if (role == DescRole) { - return ProtocolProps::protocolDescriptions().value(p); - } - if (role == ServiceTypeRole) { - return ProtocolProps::protocolService(p); - } - if (role == IsInstalledRole) { - return ContainerProps::protocolsForContainer(m_selectedDockerContainer).contains(p); + case ProtocolPageRole: + return static_cast(protocolPage(ProtocolProps::protoFromString(m_content.keys().at(index.row())))); } + return QVariant(); } -void ProtocolsModel::setSelectedServerIndex(int index) +void ProtocolsModel::updateModel(const QJsonObject &content) { - beginResetModel(); - m_selectedServerIndex = index; - endResetModel(); + m_container = ContainerProps::containerFromString(content.value(config_key::container).toString()); + + m_content = content; + m_content.remove(config_key::container); } -void ProtocolsModel::setSelectedDockerContainer(DockerContainer c) +QJsonObject ProtocolsModel::getConfig() { - beginResetModel(); - m_selectedDockerContainer = c; - endResetModel(); + QJsonObject config = m_content; + config.insert(config_key::container, ContainerProps::containerToString(m_container)); + return config; } - +PageLoader::PageEnum ProtocolsModel::protocolPage(Proto protocol) const +{ + switch (protocol) { + case Proto::OpenVpn: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; + case Proto::Cloak: return PageLoader::PageEnum::PageProtocolCloakSettings; + case Proto::ShadowSocks: return PageLoader::PageEnum::PageProtocolShadowSocksSettings; + case Proto::WireGuard: return PageLoader::PageEnum::PageProtocolWireGuardSettings; + case Proto::Ikev2: return PageLoader::PageEnum::PageProtocolIKev2Settings; + case Proto::L2tp: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; + // non-vpn + case Proto::TorWebSite: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; + case Proto::Dns: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; + case Proto::FileShare: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; + case Proto::Sftp: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; + default: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; + } +} diff --git a/client/ui/models/protocols_model.h b/client/ui/models/protocols_model.h index 48b6eeb6..1e279cfb 100644 --- a/client/ui/models/protocols_model.h +++ b/client/ui/models/protocols_model.h @@ -3,38 +3,40 @@ #include #include -#include -#include +#include "../controllers/pageController.h" #include "settings.h" -#include "containers/containers_defs.h" class ProtocolsModel : public QAbstractListModel { Q_OBJECT public: - ProtocolsModel(std::shared_ptr settings, QObject *parent = nullptr); -public: - enum SiteRoles { - NameRole = Qt::UserRole + 1, - DescRole, - ServiceTypeRole, - IsInstalledRole + enum Roles { + ProtocolNameRole = Qt::UserRole + 1, + ProtocolPageRole }; + ProtocolsModel(std::shared_ptr settings, QObject *parent = nullptr); + int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - Q_INVOKABLE void setSelectedServerIndex(int index); - Q_INVOKABLE void setSelectedDockerContainer(DockerContainer c); + +public slots: + void updateModel(const QJsonObject &content); + + QJsonObject getConfig(); protected: QHash roleNames() const override; private: - int m_selectedServerIndex; - DockerContainer m_selectedDockerContainer; + PageLoader::PageEnum protocolPage(Proto protocol) const; + std::shared_ptr m_settings; + + DockerContainer m_container; + QJsonObject m_content; }; #endif // PROTOCOLS_MODEL_H diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 1bacdd45..6fad9af6 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -123,6 +123,7 @@ bool ServersModel::isDefaultServerHasWriteAccess() void ServersModel::addServer(const QJsonObject &server) { + // todo beginInsertRows()? beginResetModel(); m_settings->addServer(server); m_servers = m_settings->serversArray(); diff --git a/client/ui/pages_logic/GeneralSettingsLogic.cpp b/client/ui/pages_logic/GeneralSettingsLogic.cpp index 0e92f8c9..141308f4 100644 --- a/client/ui/pages_logic/GeneralSettingsLogic.cpp +++ b/client/ui/pages_logic/GeneralSettingsLogic.cpp @@ -1,13 +1,11 @@ #include "GeneralSettingsLogic.h" #include "ShareConnectionLogic.h" -#include "../uilogic.h" #include "../models/protocols_model.h" +#include "../uilogic.h" -GeneralSettingsLogic::GeneralSettingsLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent) +GeneralSettingsLogic::GeneralSettingsLogic(UiLogic *logic, QObject *parent) : PageLogicBase(logic, parent) { - } void GeneralSettingsLogic::onUpdatePage() @@ -32,9 +30,11 @@ void GeneralSettingsLogic::onPushButtonGeneralSettingsShareConnectionClicked() uiLogic()->m_selectedServerIndex = m_settings->defaultServerIndex(); uiLogic()->m_selectedDockerContainer = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex); - qobject_cast(uiLogic()->protocolsModel())->setSelectedServerIndex(uiLogic()->m_selectedServerIndex); - qobject_cast(uiLogic()->protocolsModel())->setSelectedDockerContainer(uiLogic()->m_selectedDockerContainer); + // qobject_cast(uiLogic()->protocolsModel())->setSelectedServerIndex(uiLogic()->m_selectedServerIndex); qobject_cast(uiLogic()->protocolsModel())->setSelectedDockerContainer(uiLogic()->m_selectedDockerContainer); - uiLogic()->pageLogic()->updateSharingPage(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer); + uiLogic()->pageLogic()->updateSharingPage(uiLogic()->m_selectedServerIndex, + uiLogic()->m_selectedDockerContainer); emit uiLogic()->goToPage(Page::ShareConnection); } diff --git a/client/ui/pages_logic/ServerContainersLogic.cpp b/client/ui/pages_logic/ServerContainersLogic.cpp index 2b556dcc..a2971cf8 100644 --- a/client/ui/pages_logic/ServerContainersLogic.cpp +++ b/client/ui/pages_logic/ServerContainersLogic.cpp @@ -1,6 +1,6 @@ #include "ServerContainersLogic.h" -#include "ShareConnectionLogic.h" #include "ServerConfiguringProgressLogic.h" +#include "ShareConnectionLogic.h" #include @@ -9,24 +9,22 @@ #include "core/servercontroller.h" #include -#include "../uilogic.h" #include "../pages_logic/VpnLogic.h" -#include "vpnconnection.h" +#include "../uilogic.h" #include "core/errorstrings.h" +#include "vpnconnection.h" - -ServerContainersLogic::ServerContainersLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent) +ServerContainersLogic::ServerContainersLogic(UiLogic *logic, QObject *parent) : PageLogicBase(logic, parent) { } void ServerContainersLogic::onUpdatePage() { -// ContainersModel *c_model = qobject_cast(uiLogic()->containersModel()); -// c_model->setSelectedServerIndex(uiLogic()->m_selectedServerIndex); + // ContainersModel *c_model = qobject_cast(uiLogic()->containersModel()); + // c_model->setSelectedServerIndex(uiLogic()->m_selectedServerIndex); ProtocolsModel *p_model = qobject_cast(uiLogic()->protocolsModel()); - p_model->setSelectedServerIndex(uiLogic()->m_selectedServerIndex); + // p_model->setSelectedServerIndex(uiLogic()->m_selectedServerIndex); set_isManagedServer(m_settings->haveAuthData(uiLogic()->m_selectedServerIndex)); uiLogic()->m_installCredentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex); @@ -35,25 +33,29 @@ void ServerContainersLogic::onUpdatePage() void ServerContainersLogic::onPushButtonProtoSettingsClicked(DockerContainer c, Proto p) { - qDebug()<< "ServerContainersLogic::onPushButtonProtoSettingsClicked" << c << p; + qDebug() << "ServerContainersLogic::onPushButtonProtoSettingsClicked" << c << p; uiLogic()->m_selectedDockerContainer = c; - uiLogic()->protocolLogic(p)->updateProtocolPage(m_settings->protocolConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer, p), - uiLogic()->m_selectedDockerContainer, - m_settings->haveAuthData(uiLogic()->m_selectedServerIndex)); + uiLogic()->protocolLogic(p)->updateProtocolPage( + m_settings->protocolConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer, p), + uiLogic()->m_selectedDockerContainer, m_settings->haveAuthData(uiLogic()->m_selectedServerIndex)); emit uiLogic()->goToProtocolPage(p); } void ServerContainersLogic::onPushButtonDefaultClicked(DockerContainer c) { - if (m_settings->defaultContainer(uiLogic()->m_selectedServerIndex) == c) return; + if (m_settings->defaultContainer(uiLogic()->m_selectedServerIndex) == c) + return; m_settings->setDefaultContainer(uiLogic()->m_selectedServerIndex, c); uiLogic()->onUpdateAllPages(); - if (uiLogic()->m_selectedServerIndex != m_settings->defaultServerIndex()) return; - if (!uiLogic()->m_vpnConnection) return; - if (!uiLogic()->m_vpnConnection->isConnected()) return; + if (uiLogic()->m_selectedServerIndex != m_settings->defaultServerIndex()) + return; + if (!uiLogic()->m_vpnConnection) + return; + if (!uiLogic()->m_vpnConnection->isConnected()) + return; uiLogic()->pageLogic()->onDisconnect(); uiLogic()->pageLogic()->onConnect(); @@ -67,16 +69,19 @@ void ServerContainersLogic::onPushButtonShareClicked(DockerContainer c) void ServerContainersLogic::onPushButtonRemoveClicked(DockerContainer container) { - //buttonSetEnabledFunc(false); + // buttonSetEnabledFunc(false); ServerController serverController(m_settings); - ErrorCode e = serverController.removeContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), container); + ErrorCode e = + serverController.removeContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), container); m_settings->removeContainerConfig(uiLogic()->m_selectedServerIndex, container); - //buttonSetEnabledFunc(true); + // buttonSetEnabledFunc(true); if (m_settings->defaultContainer(uiLogic()->m_selectedServerIndex) == container) { const auto &c = m_settings->containers(uiLogic()->m_selectedServerIndex); - if (c.isEmpty()) m_settings->setDefaultContainer(uiLogic()->m_selectedServerIndex, DockerContainer::None); - else m_settings->setDefaultContainer(uiLogic()->m_selectedServerIndex, c.keys().first()); + if (c.isEmpty()) + m_settings->setDefaultContainer(uiLogic()->m_selectedServerIndex, DockerContainer::None); + else + m_settings->setDefaultContainer(uiLogic()->m_selectedServerIndex, c.keys().first()); } uiLogic()->onUpdateAllPages(); } @@ -96,7 +101,8 @@ void ServerContainersLogic::onPushButtonContinueClicked(DockerContainer c, int p if (!uiLogic()->isContainerAlreadyAddedToGui(c)) { auto installAction = [this, c, &config]() { ServerController serverController(m_settings); - return serverController.setupContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), c, config); + return serverController.setupContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), + c, config); }; errorCode = uiLogic()->pageLogic()->doInstallAction(installAction); @@ -107,16 +113,16 @@ void ServerContainersLogic::onPushButtonContinueClicked(DockerContainer c, int p } } } else { - emit uiLogic()->showWarningMessage("Attention! The container you are trying to install is already installed on the server. " - "All installed containers have been added to the application "); + emit uiLogic()->showWarningMessage( + "Attention! The container you are trying to install is already installed on the server. " + "All installed containers have been added to the application "); } uiLogic()->onUpdateAllPages(); } if (errorCode != ErrorCode::NoError) { - emit uiLogic()->showWarningMessage(tr("Error occurred while configuring server.") + "\n" + - tr("Error message: ") + errorString(errorCode) + "\n" + - tr("See logs for details.")); + emit uiLogic()->showWarningMessage(tr("Error occurred while configuring server.") + "\n" + tr("Error message: ") + + errorString(errorCode) + "\n" + tr("See logs for details.")); } emit uiLogic()->closePage(); } diff --git a/client/ui/qml/Components/Protocols/OpenVpnSettings.qml b/client/ui/qml/Components/Protocols/OpenVpnSettings.qml deleted file mode 100644 index f937dcfc..00000000 --- a/client/ui/qml/Components/Protocols/OpenVpnSettings.qml +++ /dev/null @@ -1,147 +0,0 @@ -import QtQuick 2.15 -import QtQuick.Controls -import QtQuick.Layouts - -import "../../Controls2" -import "../../Controls2/TextTypes" -import "../../Components" - -Item { - id: root - implicitHeight: col.implicitHeight - implicitWidth: col.implicitWidth - - ColumnLayout { - id: col - - anchors.fill: parent - - anchors.leftMargin: 16 - anchors.rightMargin: 16 - - spacing: 16 - - Header2TextType { - Layout.fillWidth: true - - text: "OpenVpn" - } - - TextFieldWithHeaderType { - Layout.fillWidth: true - - headerText: qsTr("VPN Addresses Subnet") - } - - ParagraphTextType { - Layout.fillWidth: true - - text: qsTr("Network protocol") - } - - TransportProtoSelector { - Layout.fillWidth: true - } - - TextFieldWithHeaderType { - Layout.fillWidth: true - - headerText: qsTr("Port") - } - - SwitcherType { - Layout.fillWidth: true - text: qsTr("Auto-negotiate encryption") - } - - DropDownType { - id: hash - Layout.fillWidth: true - implicitHeight: 74 - - 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 { - id: cipher - Layout.fillWidth: true - implicitHeight: 74 - - 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 { - Layout.fillWidth: true - - text: qsTr("TLS auth") - } - - CheckBoxType { - Layout.fillWidth: true - - text: qsTr("Block DNS requests outside of VPN") - } - - SwitcherType { - Layout.fillWidth: true - - text: qsTr("Additional configuration commands") - } - } -} diff --git a/client/ui/qml/Components/SettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml index 1b8ea40c..7204ac16 100644 --- a/client/ui/qml/Components/SettingsContainersListView.qml +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -6,6 +6,7 @@ import SortFilterProxyModel 0.2 import PageEnum 1.0 import ProtocolEnum 1.0 +import ContainerEnum 1.0 import "../Controls2" import "../Controls2/TextTypes" @@ -88,8 +89,32 @@ ListView { onClicked: { if (isInstalled) { - ContainersModel.setCurrentlyProcessedContainerIndex(root.model.mapToSource(index)) - goToPage(PageEnum.PageSettingsServerProtocol) + var containerIndex = root.model.mapToSource(index) + ContainersModel.setCurrentlyProcessedContainerIndex(containerIndex) + switch (containerIndex) { + case ContainerEnum.OpenVpn: { + OpenVpnConfigModel.updateModel(ProtocolsModel.getConfig()) + goToPage(PageEnum.PageProtocolOpenVpnSettings) + break + } + case ContainerEnum.WireGuard: { + WireGuardConfigModel.updateModel(ProtocolsModel.getConfig()) + goToPage(PageEnum.PageProtocolWireGuardSettings) + break + } + case ContainerEnum.Ipsec: { + Ikev2ConfigModel.updateModel(ProtocolsModel.getConfig()) + goToPage(PageEnum.PageProtocolIKev2Settings) + break + } + default: { + if (serviceType !== ProtocolEnum.Other) { //todo disable settings for dns container + ProtocolsModel.updateModel(config) + goToPage(PageEnum.PageSettingsServerProtocol) + } + } + } + } else { ContainersModel.setCurrentlyProcessedContainerIndex(root.model.mapToSource(index)) InstallController.setShouldCreateServer(false) diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index d5ed1029..275e41ea 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -75,9 +75,9 @@ DrawerType { text: qsTr("Copy") onClicked: { - configContent.selectAll() - configContent.copy() - configContent.select(0, 0) + configText.selectAll() + configText.copy() + configText.select(0, 0) } } @@ -138,6 +138,8 @@ DrawerType { } TextArea { + id: configText + Layout.fillWidth: true Layout.topMargin: 16 Layout.bottomMargin: 16 diff --git a/client/ui/qml/Components/TransportProtoSelector.qml b/client/ui/qml/Components/TransportProtoSelector.qml index dd315deb..bfd82cb1 100644 --- a/client/ui/qml/Components/TransportProtoSelector.qml +++ b/client/ui/qml/Components/TransportProtoSelector.qml @@ -25,6 +25,8 @@ Rectangle { HorizontalRadioButton { checked: root.currentIndex === 0 + hoverEnabled: root.enabled + implicitWidth: (rootWidth - 32) / 2 text: "UDP" @@ -36,6 +38,8 @@ Rectangle { HorizontalRadioButton { checked: root.currentIndex === 1 + hoverEnabled: root.enabled + implicitWidth: (rootWidth - 32) / 2 text: "TCP" diff --git a/client/ui/qml/Controls2/CheckBoxType.qml b/client/ui/qml/Controls2/CheckBoxType.qml index 1a22f326..6ce5ac4e 100644 --- a/client/ui/qml/Controls2/CheckBoxType.qml +++ b/client/ui/qml/Controls2/CheckBoxType.qml @@ -3,6 +3,8 @@ import QtQuick.Controls import QtQuick.Layouts import Qt5Compat.GraphicalEffects +import "TextTypes" + CheckBox { id: root @@ -26,6 +28,8 @@ CheckBox { indicator: Rectangle { id: checkBoxBackground + anchors.verticalCenter: parent.verticalCenter + implicitWidth: 56 implicitHeight: 56 radius: 16 @@ -57,43 +61,41 @@ CheckBox { anchors.centerIn: parent source: root.pressed ? imageSource : root.checked ? imageSource : "" - - ColorOverlay { - id: imageColor - anchors.fill: indicator - source: indicator - - color: root.pressed ? pressedImageColor : root.checked ? checkedImageColor : defaultImageColor + layer { + enabled: true + effect: ColorOverlay { + color: root.pressed ? pressedImageColor : root.checked ? checkedImageColor : defaultImageColor + } } } } } contentItem: ColumnLayout { - anchors.fill: parent + anchors.left: parent.left + anchors.right: parent.right anchors.leftMargin: 8 + checkBoxBackground.width - Text { - text: root.text - color: "#D7D8DB" - font.pixelSize: 18 - font.weight: 400 - font.family: "PT Root UI VF" + spacing: 4 - height: 22 + ListItemTitleType { Layout.fillWidth: true +// Layout.topMargin: 16 +// Layout.bottomMargin: description.visible ? 0 : 16 + + text: root.text } - Text { + CaptionTextType { + id: description + + Layout.fillWidth: true + Layout.bottomMargin: 16 + text: root.descriptionText color: "#878b91" - font.pixelSize: 13 - font.weight: 400 - font.family: "PT Root UI VF" - font.letterSpacing: 0.02 - height: 16 - Layout.fillWidth: true + visible: root.descriptionText !== "" } } diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 85989ae6..7a4538ba 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -9,8 +9,11 @@ Item { property string text property string textColor: "#d7d8db" + property string textDisabledColor: "#878B91" property string descriptionText + property string descriptionTextColor: "#878B91" + property string descriptionTextDisabledColor: "#494B50" property string headerText property string headerBackButtonImage @@ -23,7 +26,6 @@ Item { property string rootButtonHoveredBorderColor: "#494B50" property string rootButtonDefaultBorderColor: "transparent" property string rootButtonPressedBorderColor: "#D7D8DB" - property int rootButtonBorderWidth: 1 property real drawerHeight: 0.9 property Component listView @@ -36,10 +38,18 @@ Item { onMenuVisibleChanged: { if (menuVisible) { rootButtonBackground.border.color = rootButtonPressedBorderColor - rootButtonBackground.border.width = rootButtonBorderWidth } else { rootButtonBackground.border.color = rootButtonDefaultBorderColor - rootButtonBackground.border.width = 0 + } + } + + onEnabledChanged: { + if (enabled) { + rootButtonBackground.color = rootButtonBackgroundColor + rootButtonBackground.border.color = rootButtonDefaultBorderColor + } else { + rootButtonBackground.color = "transparent" + rootButtonBackground.border.color = rootButtonHoveredBorderColor } } @@ -48,13 +58,10 @@ Item { anchors.fill: rootButtonContent radius: 16 - color: rootButtonBackgroundColor - border.color: rootButtonDefaultBorderColor - border.width: 0 + color: root.enabled ? rootButtonBackgroundColor : "transparent" + border.color: root.enabled ? rootButtonDefaultBorderColor : rootButtonHoveredBorderColor + border.width: 1 - Behavior on border.width { - PropertyAnimation { duration: 200 } - } Behavior on border.color { PropertyAnimation { duration: 200 } } @@ -77,7 +84,7 @@ Item { visible: root.descriptionText !== "" - color: "#878B91" + color: root.enabled ? root.descriptionTextColor : root.descriptionTextDisabledColor text: root.descriptionText } @@ -87,7 +94,7 @@ Item { horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter - color: root.textColor + color: root.enabled ? root.textColor : root.textDisabledColor text: root.text wrapMode: Text.NoWrap @@ -108,18 +115,16 @@ Item { MouseArea { anchors.fill: rootButtonContent cursorShape: Qt.PointingHandCursor - hoverEnabled: true + hoverEnabled: root.enabled ? true : false onEntered: { if (menu.visible === false) { - rootButtonBackground.border.width = rootButtonBorderWidth rootButtonBackground.border.color = rootButtonHoveredBorderColor } } onExited: { if (menu.visible === false) { - rootButtonBackground.border.width = 0 rootButtonBackground.border.color = rootButtonDefaultBorderColor } } diff --git a/client/ui/qml/Controls2/HorizontalRadioButton.qml b/client/ui/qml/Controls2/HorizontalRadioButton.qml index 4a2a13dd..88fc2531 100644 --- a/client/ui/qml/Controls2/HorizontalRadioButton.qml +++ b/client/ui/qml/Controls2/HorizontalRadioButton.qml @@ -22,45 +22,44 @@ RadioButton { implicitWidth: content.implicitWidth implicitHeight: content.implicitHeight - hoverEnabled: true - indicator: Rectangle { anchors.fill: parent radius: 16 color: { - if (root.enabled) { +// if (root.enabled) { if (root.hovered) { return hoveredColor } else if (root.checked) { return selectedColor } return defaultColor - } else { - return disabledColor - } +// } else { +// return disabledColor +// } } border.color: { - if (root.enabled) { +// if (root.enabled) { if (root.pressed) { return pressedBorderColor } else if (root.checked) { return selectedBorderColor } - } - return defaultBodredColor + return defaultBodredColor +// } +// return defaultBodredColor } border.width: { - if (root.enabled) { +// if (root.enabled) { if(root.checked) { return 1 } return root.pressed ? 1 : 0 - } else { - return 0 - } +// } else { +// return 0 +// } } Behavior on color { diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml index 85d651ef..13a7ea6e 100644 --- a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -8,6 +8,9 @@ Item { id: root property string headerText + property string headerTextDisabledColor: "#494B50" + property string headerTextColor: "#878b91" + property alias errorText: errorField.text property string buttonText @@ -15,9 +18,18 @@ Item { property alias textField: textField property alias textFieldText: textField.text + property string textFieldTextColor: "#d7d8db" + property string textFieldTextDisabledColor: "#878B91" + property string textFieldPlaceholderText property bool textFieldEditable: true + property string borderColor: "#2C2D30" + property string borderFocusedColor: "#d7d8db" + + property string backgroundColor: "#1c1d21" + property string backgroundDisabledColor: "transparent" + implicitWidth: content.implicitWidth implicitHeight: content.implicitHeight @@ -29,9 +41,9 @@ Item { id: backgroud Layout.fillWidth: true Layout.preferredHeight: 74 - color: "#1c1d21" + color: root.enabled ? root.backgroundColor : root.backgroundDisabledColor radius: 16 - border.color: textField.focus ? "#d7d8db" : "#2C2D30" + border.color: textField.focus ? root.borderFocusedColor : root.borderColor border.width: 1 Behavior on border.color { @@ -43,7 +55,7 @@ Item { ColumnLayout { LabelTextType { text: root.headerText - color: "#878b91" + color: root.enabled ? root.headerTextColor : root.headerTextDisabledColor Layout.fillWidth: true Layout.rightMargin: 16 @@ -55,9 +67,9 @@ Item { id: textField enabled: root.textFieldEditable - color: "#d7d8db" + color: root.enabled ? root.textFieldTextColor : root.textFieldTextDisabledColor - placeholderText: textFieldPlaceholderText + placeholderText: root.textFieldPlaceholderText placeholderTextColor: "#494B50" selectionColor: "#412102" @@ -79,7 +91,7 @@ Item { background: Rectangle { anchors.fill: parent - color: "#1c1d21" + color: root.enabled ? root.backgroundColor : root.backgroundDisabledColor } onTextChanged: { @@ -98,13 +110,13 @@ Item { textColor: "#D7D8DB" borderWidth: 0 - text: buttonText + text: root.buttonText Layout.rightMargin: 24 onClicked: { - if (clickedFunc && typeof clickedFunc === "function") { - clickedFunc() + if (root.clickedFunc && typeof root.clickedFunc === "function") { + root.clickedFunc() } } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index b97acad1..ab1eeba1 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -158,7 +158,6 @@ PageType { implicitHeight: 40 - rootButtonBorderWidth: 0 rootButtonImageColor: "#0E0E11" rootButtonBackgroundColor: "#D7D8DB" diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml new file mode 100644 index 00000000..9ee67303 --- /dev/null +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -0,0 +1,176 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + //todo move to main? + Connections { + target: InstallController + + function onInstallationErrorOccurred(errorMessage) { + PageController.showErrorMessage(errorMessage) + } + + function onUpdateContainerFinished() { + //todo change to notification + PageController.showErrorMessage(qsTr("Settings updated successfully")) + } + } + + ColumnLayout { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + } + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.implicitHeight + + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + ListView { + id: listview + + width: parent.width + height: listview.contentItem.height + + clip: true + interactive: false + + model: CloakConfigModel + + delegate: Item { + implicitWidth: listview.width + implicitHeight: col.implicitHeight + + ColumnLayout { + id: col + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + spacing: 0 + + HeaderType { + Layout.fillWidth: true + + headerText: qsTr("Cloak settings") + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + Layout.topMargin: 32 + + headerText: qsTr("Masquerading as traffic from") + textFieldText: site + + textField.onEditingFinished: { + if (textFieldText !== site) { + site = textFieldText + } + } + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Port") + textFieldText: port + + textField.onEditingFinished: { + if (textFieldText !== port) { + port = textFieldText + } + } + } + + DropDownType { + id: cipherDropDown + Layout.fillWidth: true + Layout.topMargin: 16 + implicitHeight: 74 + + descriptionText: qsTr("Cipher") + headerText: qsTr("Cipher") + + listView: ListViewType { + id: cipherListView + + rootWidth: root.width + + model: ListModel { + ListElement { name : "chacha20-ietf-poly1305" } + ListElement { name : "xchacha20-ietf-poly1305" } + ListElement { name : "aes-256-gcm" } + ListElement { name : "aes-192-gcm" } + ListElement { name : "aes-128-gcm" } + } + + clickedFunction: function() { + cipherDropDown.text = selectedText + cipher = cipherDropDown.text + cipherDropDown.menuVisible = false + } + + Component.onCompleted: { + cipherDropDown.text = cipher + + for (var i = 0; i < cipherListView.model.count; i++) { + if (cipherListView.model.get(i).name === cipherDropDown.text) { + currentIndex = i + } + } + } + } + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.bottomMargin: 24 + + text: qsTr("Save and Restart Amnesia") + + onClicked: { + forceActiveFocus() + PageController.showBusyIndicator(true) + InstallController.updateContainer(CloakConfigModel.getConfig()) + PageController.showBusyIndicator(false) + } + } + } + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml new file mode 100644 index 00000000..8e5556d0 --- /dev/null +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -0,0 +1,465 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + Connections { + target: InstallController + + function onInstallationErrorOccurred(errorMessage) { + PageController.showErrorMessage(errorMessage) + } + + function onUpdateContainerFinished() { + //todo change to notification + PageController.showErrorMessage(qsTr("Settings updated successfully")) + } + } + + ColumnLayout { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + } + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.implicitHeight + + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + ListView { + id: listview + + width: parent.width + height: listview.contentItem.height + + clip: true + interactive: false + + model: OpenVpnConfigModel + + delegate: Item { + implicitWidth: listview.width + implicitHeight: col.implicitHeight + + ColumnLayout { + id: col + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + spacing: 0 + + HeaderType { + Layout.fillWidth: true + + headerText: qsTr("OpenVPN settings") + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + Layout.topMargin: 32 + + headerText: qsTr("VPN Addresses Subnet") + textFieldText: subnetAddress + + textField.onEditingFinished: { + if (textFieldText !== subnetAddress) { + subnetAddress = textFieldText + } + } + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 32 + + text: qsTr("Network protocol") + } + + 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 { + Layout.fillWidth: true + Layout.topMargin: 40 + + enabled: isPortEditable + + headerText: qsTr("Port") + textFieldText: port + + textField.onEditingFinished: { + if (textFieldText !== port) { + port = textFieldText + } + } + } + + SwitcherType { + id: autoNegotiateEncryprionSwitcher + + Layout.fillWidth: true + Layout.topMargin: 24 + + text: qsTr("Auto-negotiate encryption") + checked: autoNegotiateEncryprion + + onCheckedChanged: { + if (checked !== autoNegotiateEncryprion) { + autoNegotiateEncryprion = checked + } + } + } + + DropDownType { + id: hashDropDown + Layout.fillWidth: true + Layout.topMargin: 20 + implicitHeight: 74 + + enabled: !autoNegotiateEncryprionSwitcher.checked + + descriptionText: qsTr("Hash") + headerText: qsTr("Hash") + + listView: ListViewType { + 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.menuVisible = false + } + + 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 + implicitHeight: 74 + + enabled: !autoNegotiateEncryprionSwitcher.checked + + descriptionText: qsTr("Cipher") + headerText: qsTr("Cipher") + + listView: ListViewType { + 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.menuVisible = false + } + + 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 { + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.preferredHeight: checkboxLayout.implicitHeight + color: "#1C1D21" + radius: 16 + + ColumnLayout { + id: checkboxLayout + CheckBoxType { + Layout.fillWidth: true + + text: qsTr("TLS auth") + checked: tlsAuth + + onCheckedChanged: { + if (checked !== tlsAuth) { + tlsAuth = checked + } + } + } + + DividerType {} + + CheckBoxType { + 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 + + checked: additionalClientCommands !== "" + + text: qsTr("Additional client configuration commands") + } + + Rectangle { + Layout.fillWidth: true + Layout.topMargin: 16 + + height: 148 + color: "#1C1D21" + border.width: 1 + border.color: "#2C2D30" + radius: 16 + visible: additionalClientCommandsSwitcher.checked + + FlickableType { + anchors.top: parent.top + anchors.bottom: parent.bottom + contentHeight: additionalClientCommandsTextArea.implicitHeight + TextArea { + id: additionalClientCommandsTextArea + + width: parent.width + anchors.topMargin: 16 + anchors.bottomMargin: 16 + + topPadding: 16 + leftPadding: 16 + + color: "#D7D8DB" + selectionColor: "#412102" + selectedTextColor: "#D7D8DB" + placeholderTextColor: "#878B91" + + font.pixelSize: 16 + font.weight: Font.Medium + font.family: "PT Root UI VF" + + placeholderText: qsTr("Commands:") + text: additionalClientCommands + + wrapMode: Text.Wrap + + onEditingFinished: { + if (additionalClientCommands !== text) { + additionalClientCommands = text + } + } + } + } + } + + SwitcherType { + id: additionalServerCommandsSwitcher + Layout.fillWidth: true + Layout.topMargin: 16 + + checked: additionalServerCommands !== "" + + text: qsTr("Additional server configuration commands") + } + + Rectangle { + Layout.fillWidth: true + Layout.topMargin: 16 + + height: 148 + color: "#1C1D21" + border.width: 1 + border.color: "#2C2D30" + radius: 16 + visible: additionalServerCommandsSwitcher.checked + + FlickableType { + anchors.top: parent.top + anchors.bottom: parent.bottom + contentHeight: additionalServerCommandsTextArea.implicitHeight + TextArea { + id: additionalServerCommandsTextArea + + width: parent.width + anchors.topMargin: 16 + anchors.bottomMargin: 16 + + topPadding: 16 + leftPadding: 16 + + color: "#D7D8DB" + selectionColor: "#412102" + selectedTextColor: "#D7D8DB" + placeholderTextColor: "#878B91" + + font.pixelSize: 16 + font.weight: Font.Medium + font.family: "PT Root UI VF" + + placeholderText: qsTr("Commands:") + text: additionalServerCommands + + wrapMode: Text.Wrap + + onEditingFinished: { + if (additionalServerCommands !== text) { + additionalServerCommands = text + } + } + } + } + } + + BasicButtonType { + Layout.topMargin: 24 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + textColor: "#EB5757" + + text: qsTr("Remove OpenVPN") + + onClicked: { + questionDrawer.headerText = qsTr("Remove OpenVpn from server?") +// questionDrawer.descriptionText = qsTr("") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + goToPage(PageEnum.PageDeinstalling) + ContainersModel.removeCurrentlyProcessedContainer() + closePage() + closePage() //todo auto close to deinstall page? + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true + } + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.bottomMargin: 24 + + text: qsTr("Save and Restart Amnesia") + + onClicked: { + forceActiveFocus() + PageController.showBusyIndicator(true) + InstallController.updateContainer(OpenVpnConfigModel.getConfig()) + PageController.showBusyIndicator(false) + } + } + } + } + } + } + + QuestionDrawer { + id: questionDrawer + } + } +} diff --git a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml new file mode 100644 index 00000000..57006cc4 --- /dev/null +++ b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml @@ -0,0 +1,162 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + //todo move to main? + Connections { + target: InstallController + + function onInstallationErrorOccurred(errorMessage) { + PageController.showErrorMessage(errorMessage) + } + + function onUpdateContainerFinished() { + //todo change to notification + PageController.showErrorMessage(qsTr("Settings updated successfully")) + } + } + + ColumnLayout { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + } + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.implicitHeight + + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + ListView { + id: listview + + width: parent.width + height: listview.contentItem.height + + clip: true + interactive: false + + model: ShadowSocksConfigModel + + delegate: Item { + implicitWidth: listview.width + implicitHeight: col.implicitHeight + + ColumnLayout { + id: col + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + spacing: 0 + + HeaderType { + Layout.fillWidth: true + + headerText: qsTr("ShadowSocks settings") + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + Layout.topMargin: 40 + + headerText: qsTr("Port") + textFieldText: port + + textField.onEditingFinished: { + if (textFieldText !== port) { + port = textFieldText + } + } + } + + DropDownType { + id: cipherDropDown + Layout.fillWidth: true + Layout.topMargin: 20 + implicitHeight: 74 + + descriptionText: qsTr("Cipher") + headerText: qsTr("Cipher") + + listView: ListViewType { + id: cipherListView + + rootWidth: root.width + + model: ListModel { + ListElement { name : "chacha20-ietf-poly1305" } + ListElement { name : "xchacha20-ietf-poly1305" } + ListElement { name : "aes-256-gcm" } + ListElement { name : "aes-192-gcm" } + ListElement { name : "aes-128-gcm" } + } + + clickedFunction: function() { + cipherDropDown.text = selectedText + cipher = cipherDropDown.text + cipherDropDown.menuVisible = false + } + + Component.onCompleted: { + cipherDropDown.text = cipher + + for (var i = 0; i < cipherListView.model.count; i++) { + if (cipherListView.model.get(i).name === cipherDropDown.text) { + currentIndex = i + } + } + } + } + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.bottomMargin: 24 + + text: qsTr("Save and Restart Amnesia") + + onClicked: { + forceActiveFocus() + PageController.showBusyIndicator(true) + InstallController.updateContainer(ShadowSocksConfigModel.getConfig()) + PageController.showBusyIndicator(false) + } + } + } + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml index 498006df..ec2ca91f 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -6,6 +6,7 @@ import SortFilterProxyModel 0.2 import PageEnum 1.0 import ProtocolEnum 1.0 +import ContainerEnum 1.0 import ContainerProps 1.0 import "./" @@ -13,15 +14,37 @@ import "../Controls2" import "../Controls2/TextTypes" import "../Config" import "../Components" -import "../Components/Protocols" PageType { id: root + ColumnLayout { + id: header + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + } + + HeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: ContainersModel.getCurrentlyProcessedContainerName() + qsTr(" settings") + } + } + FlickableType { id: fl - anchors.fill: parent - contentHeight: content.height + openVpnSettings.implicitHeight + anchors.top: header.bottom + anchors.left: parent.left + anchors.right: parent.right + contentHeight: content.height Column { id: content @@ -29,8 +52,7 @@ PageType { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - - spacing: 16 + anchors.topMargin: 32 ListView { // todo change id naming @@ -39,16 +61,7 @@ PageType { height: container.contentItem.height clip: true interactive: false - model: SortFilterProxyModel { - id: proxyContainersModel - sourceModel: ContainersModel - filters: [ - ValueFilter { - roleName: "isCurrentlyProcessed" - value: true - } - ] - } + model: ProtocolsModel delegate: Item { implicitWidth: container.width @@ -58,24 +71,60 @@ PageType { id: delegateContent anchors.fill: parent - anchors.rightMargin: 16 - anchors.leftMargin: 16 - HeaderType { + LabelWithButtonType { + id: button + Layout.fillWidth: true - Layout.topMargin: 20 - headerText: name + text: protocolName + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + var containerIndex = ContainersModel.getCurrentlyProcessedContainerIndex() + switch (containerIndex) { + case ContainerEnum.OpenVpn: OpenVpnConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ContainerEnum.ShadowSocks: ShadowSocksConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ContainerEnum.Cloak: CloakConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ContainerEnum.WireGuard: WireGuardConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ContainerEnum.Ipsec: Ikev2ConfigModel.updateModel(ProtocolsModel.getConfig()); break; + } + goToPage(protocolPage); + } + + MouseArea { + anchors.fill: button + cursorShape: Qt.PointingHandCursor + enabled: false + } } + + DividerType {} } } } - OpenVpnSettings { - id: openVpnSettings + LabelWithButtonType { + id: removeButton width: parent.width + + text: qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() + textColor: "#EB5757" + + clickedFunction: function() { + ContainersModel.removeCurrentlyProcessedContainer() + closePage() + } + + MouseArea { + anchors.fill: removeButton + cursorShape: Qt.PointingHandCursor + enabled: false + } } + + DividerType {} } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index f1f5324e..2f07db18 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -14,8 +14,6 @@ import "../Config" PageType { id: root - property real progressBarValue: 0 - Connections { target: InstallController @@ -128,8 +126,6 @@ PageType { Layout.fillWidth: true Layout.topMargin: 32 -// value: progressBarValue - Timer { id: timer diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index f343fabf..6a8e301f 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -44,7 +44,7 @@ PageType { FlickableType { id: fl - anchors.top: backButton.top + anchors.top: backButton.bottom anchors.bottom: parent.bottom contentHeight: content.implicitHeight + content.anchors.topMargin + content.anchors.bottomMargin diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index c83b59d5..903e5658 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -31,8 +31,6 @@ PageType { shareConnectionDrawer.contentVisible = false PageController.showBusyIndicator(true) - console.log(type) - switch (type) { case PageShare.ConfigType.AmneziaConnection: ExportController.generateConnectionConfig(); break; case PageShare.ConfigType.AmenziaFullAccess: ExportController.generateFullAccessConfig(); break; @@ -51,6 +49,7 @@ PageType { } property bool showContent: false + property bool shareButtonEnabled: false property list connectionTypesModel: [ amneziaConnectionFormat ] @@ -180,6 +179,7 @@ PageType { serverSelector.text = selectedText ServersModel.currentlyProcessedIndex = currentIndex protocolSelector.visible = true + root.shareButtonEnabled = false } Component.onCompleted: { @@ -253,18 +253,24 @@ PageType { currentIndex: 0 clickedFunction: function () { - serverSelector.text += ", " + selectedText - shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text - shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text - ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(currentIndex)) + handler() protocolSelector.visible = false serverSelector.menuVisible = false - - fillConnectionTypeModel() } Component.onCompleted: { + handler() + } + + function handler() { + if (!proxyContainersModel.count) { + root.shareButtonEnabled = false + return + } else { + root.shareButtonEnabled = true + } + serverSelector.text += ", " + selectedText shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text @@ -336,6 +342,8 @@ PageType { Layout.fillWidth: true Layout.topMargin: 32 + enabled: shareButtonEnabled + text: qsTr("Share") onClicked: { From 3aaa7b62eff5f532a11b8fcff632878dabd7017f Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 14 Jul 2023 13:14:50 +0900 Subject: [PATCH 043/278] added page to display raw config --- client/amnezia_application.cpp | 15 ++ client/amnezia_application.h | 2 + client/resources.qrc | 1 + client/ui/controllers/exportController.cpp | 31 ++- client/ui/controllers/exportController.h | 7 +- client/ui/controllers/pageController.h | 4 +- client/ui/models/protocols_model.cpp | 14 ++ client/ui/models/protocols_model.h | 3 +- .../Components/SettingsContainersListView.qml | 13 +- .../qml/Components/ShareConnectionDrawer.qml | 2 +- client/ui/qml/Pages2/PageHome.qml | 2 +- .../qml/Pages2/PageProtocolCloakSettings.qml | 2 + .../Pages2/PageProtocolOpenVpnSettings.qml | 2 + client/ui/qml/Pages2/PageProtocolRaw.qml | 190 ++++++++++++++++++ .../PageProtocolShadowSocksSettings.qml | 2 + client/ui/qml/main2.qml | 6 + 16 files changed, 266 insertions(+), 30 deletions(-) create mode 100644 client/ui/qml/Pages2/PageProtocolRaw.qml diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 436dfc3f..abd839ed 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -86,6 +86,21 @@ void AmneziaApplication::init() initModels(); initControllers(); + m_notificationHandler.reset(NotificationHandler::create(nullptr)); + + connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(), + &NotificationHandler::setConnectionState); + + void openConnection(); + void closeConnection(); + + connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(), + &PageController::raise); + connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(), + &ConnectionController::openConnection); + connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(), + &ConnectionController::closeConnection); + // m_engine->load(url); diff --git a/client/amnezia_application.h b/client/amnezia_application.h index fabc7818..6e9fcdf0 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -22,6 +22,7 @@ #include "ui/models/containers_model.h" #include "ui/models/languageModel.h" #include "ui/models/protocols/cloakConfigModel.h" +#include "ui/notificationhandler.h" #ifdef Q_OS_WINDOWS #include "ui/models/protocols/ikev2ConfigModel.h" #endif @@ -91,6 +92,7 @@ private: #endif QSharedPointer m_vpnConnection; + QScopedPointer m_notificationHandler; QScopedPointer m_connectionController; QScopedPointer m_pageController; diff --git a/client/resources.qrc b/client/resources.qrc index dd75555f..df6fcb3e 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -273,5 +273,6 @@ ui/qml/Pages2/PageProtocolOpenVpnSettings.qml ui/qml/Pages2/PageProtocolShadowSocksSettings.qml ui/qml/Pages2/PageProtocolCloakSettings.qml + ui/qml/Pages2/PageProtocolRaw.qml diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index 5cd9a83d..c989422d 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -36,10 +36,9 @@ void ExportController::generateFullAccessConfig() QByteArray compressedConfig = QJsonDocument(config).toJson(); compressedConfig = qCompress(compressedConfig, 8); - m_rawConfig = QString("vpn://%1") - .arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding - | QByteArray::OmitTrailingEquals))); - m_formattedConfig = m_rawConfig; + m_config = QString("vpn://%1") + .arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding + | QByteArray::OmitTrailingEquals))); m_qrCodes = generateQrCodeImageSeries(compressedConfig); emit exportConfigChanged(); @@ -88,10 +87,9 @@ void ExportController::generateConnectionConfig() QByteArray compressedConfig = QJsonDocument(config).toJson(); compressedConfig = qCompress(compressedConfig, 8); - m_rawConfig = QString("vpn://%1") - .arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding - | QByteArray::OmitTrailingEquals))); - m_formattedConfig = m_rawConfig; + m_config = QString("vpn://%1") + .arg(QString(compressedConfig.toBase64(QByteArray::Base64UrlEncoding + | QByteArray::OmitTrailingEquals))); m_qrCodes = generateQrCodeImageSeries(compressedConfig); emit exportConfigChanged(); @@ -120,12 +118,10 @@ void ExportController::generateOpenVpnConfig() } config = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::OpenVpn, config); - m_rawConfig = config; - auto configJson = QJsonDocument::fromJson(config.toUtf8()).object(); QStringList lines = configJson.value(config_key::config).toString().replace("\r", "").split("\n"); for (const QString &line : lines) { - m_formattedConfig.append(line + "\n"); + m_config.append(line + "\n"); } emit exportConfigChanged(); @@ -154,20 +150,18 @@ void ExportController::generateWireGuardConfig() } config = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::WireGuard, config); - m_rawConfig = config; - auto configJson = QJsonDocument::fromJson(config.toUtf8()).object(); QStringList lines = configJson.value(config_key::config).toString().replace("\r", "").split("\n"); for (const QString &line : lines) { - m_formattedConfig.append(line + "\n"); + m_config.append(line + "\n"); } emit exportConfigChanged(); } -QString ExportController::getFormattedConfig() +QString ExportController::getConfig() { - return m_formattedConfig; + return m_config; } QList ExportController::getQrCodes() @@ -193,7 +187,7 @@ void ExportController::saveFile() QFile save(fileName.toLocalFile()); save.open(QIODevice::WriteOnly); - save.write(m_rawConfig.toUtf8()); + save.write(m_config.toUtf8()); save.close(); QFileInfo fi(fileName.toLocalFile()); @@ -233,7 +227,6 @@ int ExportController::getQrCodesCount() void ExportController::clearPreviousConfig() { - m_rawConfig.clear(); - m_formattedConfig.clear(); + m_config.clear(); m_qrCodes.clear(); } diff --git a/client/ui/controllers/exportController.h b/client/ui/controllers/exportController.h index 85144978..e4a37a96 100644 --- a/client/ui/controllers/exportController.h +++ b/client/ui/controllers/exportController.h @@ -18,7 +18,7 @@ public: Q_PROPERTY(QList qrCodes READ getQrCodes NOTIFY exportConfigChanged) Q_PROPERTY(int qrCodesCount READ getQrCodesCount NOTIFY exportConfigChanged) - Q_PROPERTY(QString formattedConfig READ getFormattedConfig NOTIFY exportConfigChanged) + Q_PROPERTY(QString config READ getConfig NOTIFY exportConfigChanged) public slots: void generateFullAccessConfig(); @@ -26,7 +26,7 @@ public slots: void generateOpenVpnConfig(); void generateWireGuardConfig(); - QString getFormattedConfig(); + QString getConfig(); QList getQrCodes(); void saveFile(); @@ -50,8 +50,7 @@ private: std::shared_ptr m_settings; std::shared_ptr m_configurator; - QString m_rawConfig; - QString m_formattedConfig; + QString m_config; QList m_qrCodes; }; diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 468068b8..05b8fbf9 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -42,7 +42,8 @@ namespace PageLoader PageProtocolShadowSocksSettings, PageProtocolCloakSettings, PageProtocolWireGuardSettings, - PageProtocolIKev2Settings + PageProtocolIKev2Settings, + PageProtocolRaw }; Q_ENUM_NS(PageEnum) @@ -70,6 +71,7 @@ signals: void showErrorMessage(QString errorMessage); void showInfoMessage(QString message); void showBusyIndicator(bool visible); + void raise(); private: QSharedPointer m_serversModel; diff --git a/client/ui/models/protocols_model.cpp b/client/ui/models/protocols_model.cpp index ac271eb9..da730f55 100644 --- a/client/ui/models/protocols_model.cpp +++ b/client/ui/models/protocols_model.cpp @@ -17,6 +17,7 @@ QHash ProtocolsModel::roleNames() const roles[ProtocolNameRole] = "protocolName"; roles[ProtocolPageRole] = "protocolPage"; + roles[RawConfigRole] = "rawConfig"; return roles; } @@ -34,6 +35,19 @@ QVariant ProtocolsModel::data(const QModelIndex &index, int role) const } case ProtocolPageRole: return static_cast(protocolPage(ProtocolProps::protoFromString(m_content.keys().at(index.row())))); + case RawConfigRole: { + auto protocolConfig = m_content.value(ContainerProps::containerTypeToString(m_container)).toObject(); + auto lastConfigJsonDoc = + QJsonDocument::fromJson(protocolConfig.value(config_key::last_config).toString().toUtf8()); + auto lastConfigJson = lastConfigJsonDoc.object(); + + QString rawConfig; + QStringList lines = lastConfigJson.value(config_key::config).toString().replace("\r", "").split("\n"); + for (const QString &l : lines) { + rawConfig.append(l + "\n"); + } + return rawConfig; + } } return QVariant(); diff --git a/client/ui/models/protocols_model.h b/client/ui/models/protocols_model.h index 1e279cfb..c4ad5c70 100644 --- a/client/ui/models/protocols_model.h +++ b/client/ui/models/protocols_model.h @@ -13,7 +13,8 @@ class ProtocolsModel : public QAbstractListModel public: enum Roles { ProtocolNameRole = Qt::UserRole + 1, - ProtocolPageRole + ProtocolPageRole, + RawConfigRole }; ProtocolsModel(std::shared_ptr settings, QObject *parent = nullptr); diff --git a/client/ui/qml/Components/SettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml index 7204ac16..d2f3ee81 100644 --- a/client/ui/qml/Components/SettingsContainersListView.qml +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -7,6 +7,7 @@ import SortFilterProxyModel 0.2 import PageEnum 1.0 import ProtocolEnum 1.0 import ContainerEnum 1.0 +import ContainerProps 1.0 import "../Controls2" import "../Controls2/TextTypes" @@ -91,19 +92,25 @@ ListView { if (isInstalled) { var containerIndex = root.model.mapToSource(index) ContainersModel.setCurrentlyProcessedContainerIndex(containerIndex) + if (config[ContainerProps.containerTypeToString(containerIndex)]["isThirdPartyConfig"]) { + ProtocolsModel.updateModel(config) + goToPage(PageEnum.PageProtocolRaw) + return + } + switch (containerIndex) { case ContainerEnum.OpenVpn: { - OpenVpnConfigModel.updateModel(ProtocolsModel.getConfig()) + OpenVpnConfigModel.updateModel(config) goToPage(PageEnum.PageProtocolOpenVpnSettings) break } case ContainerEnum.WireGuard: { - WireGuardConfigModel.updateModel(ProtocolsModel.getConfig()) + WireGuardConfigModel.updateModel(config) goToPage(PageEnum.PageProtocolWireGuardSettings) break } case ContainerEnum.Ipsec: { - Ikev2ConfigModel.updateModel(ProtocolsModel.getConfig()) + Ikev2ConfigModel.updateModel(config) goToPage(PageEnum.PageProtocolIKev2Settings) break } diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 275e41ea..05df413c 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -156,7 +156,7 @@ DrawerType { font.weight: Font.Medium font.family: "PT Root UI VF" - text: ExportController.formattedConfig + text: ExportController.config wrapMode: Text.Wrap diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index ab1eeba1..b7d44c78 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -102,7 +102,7 @@ PageType { description += "Amnezia DNS | " } } else { - if (ServersModel.isDefaultServerConfigContainsAmneziaDns) { + if (ServersModel.isDefaultServerConfigContainsAmneziaDns()) { description += "Amnezia DNS | " } } diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml index 9ee67303..33d231b5 100644 --- a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -53,6 +53,8 @@ PageType { anchors.left: parent.left anchors.right: parent.right + enabled: ServersModel.isCurrentlyProcessedServerHasWriteAccess() + ListView { id: listview diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index 8e5556d0..0bad68e9 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -54,6 +54,8 @@ PageType { anchors.left: parent.left anchors.right: parent.right + enabled: ServersModel.isCurrentlyProcessedServerHasWriteAccess() + ListView { id: listview diff --git a/client/ui/qml/Pages2/PageProtocolRaw.qml b/client/ui/qml/Pages2/PageProtocolRaw.qml new file mode 100644 index 00000000..377d948e --- /dev/null +++ b/client/ui/qml/Pages2/PageProtocolRaw.qml @@ -0,0 +1,190 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 +import ContainerEnum 1.0 +import ContainerProps 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + ColumnLayout { + id: header + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + } + + HeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: ContainersModel.getCurrentlyProcessedContainerName() + qsTr(" settings") + } + } + + FlickableType { + id: fl + anchors.top: header.bottom + anchors.left: parent.left + anchors.right: parent.right + contentHeight: content.height + + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 32 + + ListView { + width: parent.width + height: contentItem.height + clip: true + interactive: false + model: ProtocolsModel + + delegate: Item { + implicitWidth: parent.width + implicitHeight: delegateContent.implicitHeight + + ColumnLayout { + id: delegateContent + + anchors.fill: parent + + LabelWithButtonType { + id: button + + Layout.fillWidth: true + + text: qsTr("Show connection options") + + clickedFunction: function() { + configContentDrawer.open() + } + + MouseArea { + anchors.fill: button + cursorShape: Qt.PointingHandCursor + enabled: false + } + } + + DividerType {} + + DrawerType { + id: configContentDrawer + + width: parent.width + height: parent.height * 0.9 + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 + + backButtonFunction: function() { + configContentDrawer.visible = false + } + } + + FlickableType { + anchors.top: backButton.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + contentHeight: configContent.implicitHeight + configContent.anchors.topMargin + configContent.anchors.bottomMargin + + ColumnLayout { + id: configContent + + anchors.fill: parent + anchors.rightMargin: 16 + anchors.leftMargin: 16 + + Header2Type { + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Connection options ") + protocolName + } + + TextArea { + id: configText + + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.bottomMargin: 16 + + padding: 0 + leftPadding: 0 + height: 24 + + color: "#D7D8DB" + selectionColor: "#412102" + selectedTextColor: "#D7D8DB" + + font.pixelSize: 16 + font.weight: Font.Medium + font.family: "PT Root UI VF" + + text: rawConfig + + wrapMode: Text.Wrap + + background: Rectangle { + color: "transparent" + } + } + } + } + } + } + } + } + + LabelWithButtonType { + id: removeButton + + width: parent.width + + text: qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() + textColor: "#EB5757" + + clickedFunction: function() { + ContainersModel.removeCurrentlyProcessedContainer() + closePage() + } + + MouseArea { + anchors.fill: removeButton + cursorShape: Qt.PointingHandCursor + enabled: false + } + } + + DividerType {} + } + } +} diff --git a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml index 57006cc4..730e3907 100644 --- a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml @@ -53,6 +53,8 @@ PageType { anchors.left: parent.left anchors.right: parent.right + enabled: ServersModel.isCurrentlyProcessedServerHasWriteAccess() + ListView { id: listview diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index 0be8e368..8ad71d0f 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -48,5 +48,11 @@ Window { } rootStackView.replace(pagePath, { "objectName" : pagePath }) } + + function onRaise() { + root.show() + root.raise() + root.requestActivate() + } } } From 75489c00c28c4c7022ede605789183eecf093042 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 14 Jul 2023 22:59:49 +0900 Subject: [PATCH 044/278] added button 'Reset settings and remove all data from the application' --- client/core/servercontroller.cpp | 486 +++++++++--------- client/core/servercontroller.h | 59 ++- client/resources.qrc | 1 + client/secure_qsettings.cpp | 55 +- client/secure_qsettings.h | 19 +- client/settings.cpp | 80 +-- client/settings.h | 2 + client/ui/controllers/installController.cpp | 17 +- client/ui/controllers/pageController.h | 1 + client/ui/controllers/settingsController.cpp | 10 +- client/ui/controllers/settingsController.h | 8 +- client/ui/models/languageModel.cpp | 5 + client/ui/models/languageModel.h | 1 + .../ui/pages_logic/ServerContainersLogic.cpp | 2 +- .../ui/qml/Controls2/LabelWithButtonType.qml | 9 +- .../ui/qml/Pages2/PageSettingsApplication.qml | 34 ++ client/ui/qml/Pages2/PageSettingsBackup.qml | 90 ---- client/ui/qml/Pages2/PageSettingsLogging.qml | 138 +++++ 18 files changed, 585 insertions(+), 432 deletions(-) create mode 100644 client/ui/qml/Pages2/PageSettingsLogging.qml diff --git a/client/core/servercontroller.cpp b/client/core/servercontroller.cpp index 7f4690dc..409399dc 100644 --- a/client/core/servercontroller.cpp +++ b/client/core/servercontroller.cpp @@ -1,23 +1,23 @@ #include "servercontroller.h" +#include #include #include -#include #include +#include +#include +#include +#include #include #include -#include -#include -#include -#include #include -#include #include +#include #include #include -#include #include +#include #include #include @@ -25,15 +25,14 @@ #include "containers/containers_defs.h" #include "logger.h" +#include "scripts_registry.h" #include "server_defs.h" #include "settings.h" -#include "scripts_registry.h" #include "utilities.h" #include -ServerController::ServerController(std::shared_ptr settings, QObject *parent) : - m_settings(settings) +ServerController::ServerController(std::shared_ptr settings, QObject *parent) : m_settings(settings) { } @@ -42,10 +41,10 @@ ServerController::~ServerController() m_sshClient.disconnectFromHost(); } - ErrorCode ServerController::runScript(const ServerCredentials &credentials, QString script, - const std::function &cbReadStdOut, - const std::function &cbReadStdErr) { + const std::function &cbReadStdOut, + const std::function &cbReadStdErr) +{ auto error = m_sshClient.connectToHost(credentials); if (error != ErrorCode::NoError) { @@ -82,7 +81,6 @@ ErrorCode ServerController::runScript(const ServerCredentials &credentials, QStr qDebug().noquote() << "EXEC" << lineToExec; Logger::appendSshLog("Run command:" + lineToExec); - error = m_sshClient.executeCommand(lineToExec, cbReadStdOut, cbReadStdErr); if (error != ErrorCode::NoError) { return error; @@ -93,36 +91,36 @@ ErrorCode ServerController::runScript(const ServerCredentials &credentials, QStr return ErrorCode::NoError; } -ErrorCode ServerController::runContainerScript(const ServerCredentials &credentials, - DockerContainer container, QString script, - const std::function &cbReadStdOut, - const std::function &cbReadStdErr) +ErrorCode +ServerController::runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script, + const std::function &cbReadStdOut, + const std::function &cbReadStdErr) { QString fileName = "/opt/amnezia/" + Utils::getRandomString(16) + ".sh"; Logger::appendSshLog("Run container script for " + ContainerProps::containerToString(container) + ":\n" + script); ErrorCode e = uploadTextFileToContainer(container, credentials, script, fileName); - if (e) return e; + if (e) + return e; QString runner = QString("sudo docker exec -i $CONTAINER_NAME bash %1 ").arg(fileName); - e = runScript(credentials, - replaceVars(runner, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr); + e = runScript(credentials, replaceVars(runner, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr); QString remover = QString("sudo docker exec -i $CONTAINER_NAME rm %1 ").arg(fileName); - runScript(credentials, - replaceVars(remover, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr); + runScript(credentials, replaceVars(remover, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr); return e; } -ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, - const ServerCredentials &credentials, const QString &file, const QString &path, - libssh::SftpOverwriteMode overwriteMode) +ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials, + const QString &file, const QString &path, + libssh::SftpOverwriteMode overwriteMode) { ErrorCode e = ErrorCode::NoError; QString tmpFileName = QString("/tmp/%1.tmp").arg(Utils::getRandomString(16)); e = uploadFileToHost(credentials, file.toUtf8(), tmpFileName); - if (e) return e; + if (e) + return e; QString stdOut; auto cbReadStd = [&](const QString &data, libssh::Client &) { @@ -131,61 +129,63 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, }; // mkdir - QString mkdir = QString("sudo docker exec -i $CONTAINER_NAME mkdir -p \"$(dirname %1)\"") - .arg(path); - - e = runScript(credentials, - replaceVars(mkdir, genVarsForScript(credentials, container))); - if (e) return e; + QString mkdir = QString("sudo docker exec -i $CONTAINER_NAME mkdir -p \"$(dirname %1)\"").arg(path); + e = runScript(credentials, replaceVars(mkdir, genVarsForScript(credentials, container))); + if (e) + return e; if (overwriteMode == libssh::SftpOverwriteMode::SftpOverwriteExisting) { e = runScript(credentials, - replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(path), - genVarsForScript(credentials, container)), cbReadStd, cbReadStd); + replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(path), + genVarsForScript(credentials, container)), + cbReadStd, cbReadStd); - if (e) return e; - } - else if (overwriteMode == libssh::SftpOverwriteMode::SftpAppendToExisting) { + if (e) + return e; + } else if (overwriteMode == libssh::SftpOverwriteMode::SftpAppendToExisting) { e = runScript(credentials, - replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(tmpFileName), - genVarsForScript(credentials, container)), cbReadStd, cbReadStd); + replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(tmpFileName), + genVarsForScript(credentials, container)), + cbReadStd, cbReadStd); - if (e) return e; + if (e) + return e; - e = runScript(credentials, - replaceVars(QString("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName).arg(path), - genVarsForScript(credentials, container)), cbReadStd, cbReadStd); - - if (e) return e; - } - else return ErrorCode::NotImplementedError; + e = runScript( + credentials, + replaceVars( + QString("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName).arg(path), + genVarsForScript(credentials, container)), + cbReadStd, cbReadStd); + if (e) + return e; + } else + return ErrorCode::NotImplementedError; if (stdOut.contains("Error: No such container:")) { return ErrorCode::ServerContainerMissingError; } runScript(credentials, - replaceVars(QString("sudo shred %1").arg(tmpFileName), - genVarsForScript(credentials, container))); + replaceVars(QString("sudo shred %1").arg(tmpFileName), genVarsForScript(credentials, container))); - runScript(credentials, - replaceVars(QString("sudo rm %1").arg(tmpFileName), - genVarsForScript(credentials, container))); + runScript(credentials, replaceVars(QString("sudo rm %1").arg(tmpFileName), genVarsForScript(credentials, container))); return e; } -QByteArray ServerController::getTextFileFromContainer(DockerContainer container, - const ServerCredentials &credentials, const QString &path, ErrorCode *errorCode) +QByteArray ServerController::getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials, + const QString &path, ErrorCode *errorCode) { - if (errorCode) *errorCode = ErrorCode::NoError; - - QString script = QString("sudo docker exec -i %1 sh -c \"xxd -p \'%2\'\""). - arg(ContainerProps::containerToString(container)).arg(path); + if (errorCode) + *errorCode = ErrorCode::NoError; + QString script = QString("sudo docker exec -i %1 sh -c \"xxd -p \'%2\'\"") + .arg(ContainerProps::containerToString(container)) + .arg(path); QString stdOut; auto cbReadStdOut = [&](const QString &data, libssh::Client &) { @@ -197,14 +197,13 @@ QByteArray ServerController::getTextFileFromContainer(DockerContainer container, qDebug().noquote() << "Copy file from container stdout : \n" << stdOut; - - qDebug().noquote() << "Copy file from container END : \n" ; + qDebug().noquote() << "Copy file from container END : \n"; return QByteArray::fromHex(stdOut.toUtf8()); } -ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath, - libssh::SftpOverwriteMode overwriteMode) +ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, + const QString &remotePath, libssh::SftpOverwriteMode overwriteMode) { auto error = m_sshClient.connectToHost(credentials); if (error != ErrorCode::NoError) { @@ -218,7 +217,8 @@ ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credential qDebug() << "remotePath" << remotePath; - error = m_sshClient.sftpFileCopy(overwriteMode, localFile.fileName().toStdString(), remotePath.toStdString(), "non_desc"); + error = m_sshClient.sftpFileCopy(overwriteMode, localFile.fileName().toStdString(), remotePath.toStdString(), + "non_desc"); if (error != ErrorCode::NoError) { return error; } @@ -227,41 +227,45 @@ ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credential ErrorCode ServerController::removeAllContainers(const ServerCredentials &credentials) { - return runScript(credentials, - amnezia::scriptData(SharedScriptType::remove_all_containers)); + return runScript(credentials, amnezia::scriptData(SharedScriptType::remove_all_containers)); } ErrorCode ServerController::removeContainer(const ServerCredentials &credentials, DockerContainer container) { return runScript(credentials, - replaceVars(amnezia::scriptData(SharedScriptType::remove_container), - genVarsForScript(credentials, container))); + replaceVars(amnezia::scriptData(SharedScriptType::remove_container), + genVarsForScript(credentials, container))); } ErrorCode ServerController::setupContainer(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config, bool isUpdate) { qDebug().noquote() << "ServerController::setupContainer" << ContainerProps::containerToString(container); - //qDebug().noquote() << QJsonDocument(config).toJson(); + // qDebug().noquote() << QJsonDocument(config).toJson(); ErrorCode e = ErrorCode::NoError; e = isUserInSudo(credentials, container); - if (e) return e; + if (e) + return e; if (!isUpdate) { e = isServerPortBusy(credentials, container, config); - if (e) return e; + if (e) + return e; } e = isServerDpkgBusy(credentials, container); - if (e) return e; + if (e) + return e; e = installDockerWorker(credentials, container); - if (e) return e; + if (e) + return e; qDebug().noquote() << "ServerController::setupContainer installDockerWorker finished"; e = prepareHostWorker(credentials, container, config); - if (e) return e; + if (e) + return e; qDebug().noquote() << "ServerController::setupContainer prepareHostWorker finished"; removeContainer(credentials, container); @@ -269,15 +273,18 @@ ErrorCode ServerController::setupContainer(const ServerCredentials &credentials, qDebug().noquote() << "buildContainerWorker start"; e = buildContainerWorker(credentials, container, config); - if (e) return e; + if (e) + return e; qDebug().noquote() << "ServerController::setupContainer buildContainerWorker finished"; e = runContainerWorker(credentials, container, config); - if (e) return e; + if (e) + return e; qDebug().noquote() << "ServerController::setupContainer runContainerWorker finished"; e = configureContainerWorker(credentials, container, config); - if (e) return e; + if (e) + return e; qDebug().noquote() << "ServerController::setupContainer configureContainerWorker finished"; setupServerFirewall(credentials); @@ -287,46 +294,25 @@ ErrorCode ServerController::setupContainer(const ServerCredentials &credentials, } ErrorCode ServerController::updateContainer(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &oldConfig, QJsonObject &newConfig) + const QJsonObject &oldConfig, QJsonObject &newConfig) { bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig); - qDebug() << "ServerController::updateContainer for container" << container << "reinstall required is" << reinstallRequired; + qDebug() << "ServerController::updateContainer for container" << container << "reinstall required is" + << reinstallRequired; if (reinstallRequired) { return setupContainer(credentials, container, newConfig, true); - } - else { + } else { ErrorCode e = configureContainerWorker(credentials, container, newConfig); - if (e) return e; + if (e) + return e; return startupContainerWorker(credentials, container, newConfig); } } -QJsonObject ServerController::createContainerInitialConfig(DockerContainer container, int port, TransportProto tp) -{ - Proto mainProto = ContainerProps::defaultProtocol(container); - - QJsonObject config { - { config_key::container, ContainerProps::containerToString(container) } - }; - - QJsonObject protoConfig; - protoConfig.insert(config_key::port, QString::number(port)); - protoConfig.insert(config_key::transport_proto, ProtocolProps::transportProtoToString(tp, mainProto)); - - - if (container == DockerContainer::Sftp) { - protoConfig.insert(config_key::userName, protocols::sftp::defaultUserName); - protoConfig.insert(config_key::password, Utils::getRandomString(10)); - } - - config.insert(ProtocolProps::protoToString(mainProto), protoConfig); - - return config; -} - -bool ServerController::isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig) +bool ServerController::isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, + const QJsonObject &newConfig) { Proto mainProto = ContainerProps::defaultProtocol(container); @@ -334,25 +320,25 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c const QJsonObject &newProtoConfig = newConfig.value(ProtocolProps::protoToString(mainProto)).toObject(); if (container == DockerContainer::OpenVpn) { - if (oldProtoConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto) != - newProtoConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto)) - return true; + if (oldProtoConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto) + != newProtoConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto)) + return true; - if (oldProtoConfig.value(config_key::port).toString(protocols::openvpn::defaultPort) != - newProtoConfig.value(config_key::port).toString(protocols::openvpn::defaultPort)) - return true; + if (oldProtoConfig.value(config_key::port).toString(protocols::openvpn::defaultPort) + != newProtoConfig.value(config_key::port).toString(protocols::openvpn::defaultPort)) + return true; } if (container == DockerContainer::Cloak) { - if (oldProtoConfig.value(config_key::port).toString(protocols::cloak::defaultPort) != - newProtoConfig.value(config_key::port).toString(protocols::cloak::defaultPort)) - return true; + if (oldProtoConfig.value(config_key::port).toString(protocols::cloak::defaultPort) + != newProtoConfig.value(config_key::port).toString(protocols::cloak::defaultPort)) + return true; } if (container == DockerContainer::ShadowSocks) { - if (oldProtoConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort) != - newProtoConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort)) - return true; + if (oldProtoConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort) + != newProtoConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort)) + return true; } return false; @@ -374,75 +360,88 @@ ErrorCode ServerController::installDockerWorker(const ServerCredentials &credent return ErrorCode::NoError; }; - ErrorCode error = runScript(credentials, - replaceVars(amnezia::scriptData(SharedScriptType::install_docker), - genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr); + ErrorCode error = + runScript(credentials, + replaceVars(amnezia::scriptData(SharedScriptType::install_docker), genVarsForScript(credentials)), + cbReadStdOut, cbReadStdErr); - if (stdOut.contains("command not found")) return ErrorCode::ServerDockerFailedError; + if (stdOut.contains("command not found")) + return ErrorCode::ServerDockerFailedError; return error; } -ErrorCode ServerController::prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config) +ErrorCode ServerController::prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &config) { // create folder on host - return runScript(credentials, - replaceVars(amnezia::scriptData(SharedScriptType::prepare_host), - genVarsForScript(credentials, container))); + return runScript( + credentials, + replaceVars(amnezia::scriptData(SharedScriptType::prepare_host), genVarsForScript(credentials, container))); } -ErrorCode ServerController::buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config) +ErrorCode ServerController::buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &config) { ErrorCode e = uploadFileToHost(credentials, amnezia::scriptData(ProtocolScriptType::dockerfile, container).toUtf8(), - amnezia::server::getDockerfileFolder(container) + "/Dockerfile"); + amnezia::server::getDockerfileFolder(container) + "/Dockerfile"); - if (e) return e; + if (e) + return e; QString stdOut; auto cbReadStdOut = [&](const QString &data, libssh::Client &) { stdOut += data + "\n"; return ErrorCode::NoError; }; -// auto cbReadStdErr = [&](const QString &data, QSharedPointer proc) { -// stdOut += data + "\n"; -// }; + // auto cbReadStdErr = [&](const QString &data, QSharedPointer proc) { + // stdOut += data + "\n"; + // }; e = runScript(credentials, - replaceVars(amnezia::scriptData(SharedScriptType::build_container), - genVarsForScript(credentials, container, config)), cbReadStdOut); - if (e) return e; + replaceVars(amnezia::scriptData(SharedScriptType::build_container), + genVarsForScript(credentials, container, config)), + cbReadStdOut); + if (e) + return e; return e; } -ErrorCode ServerController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config) +ErrorCode ServerController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container, + QJsonObject &config) { QString stdOut; auto cbReadStdOut = [&](const QString &data, libssh::Client &) { stdOut += data + "\n"; return ErrorCode::NoError; }; - // auto cbReadStdErr = [&](const QString &data, QSharedPointer proc) { - // stdOut += data + "\n"; - // }; + // auto cbReadStdErr = [&](const QString &data, QSharedPointer proc) { + // stdOut += data + "\n"; + // }; ErrorCode e = runScript(credentials, - replaceVars(amnezia::scriptData(ProtocolScriptType::run_container, container), - genVarsForScript(credentials, container, config)), cbReadStdOut); + replaceVars(amnezia::scriptData(ProtocolScriptType::run_container, container), + genVarsForScript(credentials, container, config)), + cbReadStdOut); qDebug() << "cbReadStdOut: " << stdOut; + if (stdOut.contains("docker: Error response from daemon")) + return ErrorCode::ServerDockerFailedError; - if (stdOut.contains("docker: Error response from daemon")) return ErrorCode::ServerDockerFailedError; - - if (stdOut.contains("address already in use")) return ErrorCode::ServerPortAlreadyAllocatedError; - if (stdOut.contains("is already in use by container")) return ErrorCode::ServerPortAlreadyAllocatedError; - if (stdOut.contains("invalid publish")) return ErrorCode::ServerDockerFailedError; + if (stdOut.contains("address already in use")) + return ErrorCode::ServerPortAlreadyAllocatedError; + if (stdOut.contains("is already in use by container")) + return ErrorCode::ServerPortAlreadyAllocatedError; + if (stdOut.contains("invalid publish")) + return ErrorCode::ServerDockerFailedError; return e; } -ErrorCode ServerController::configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config) +ErrorCode ServerController::configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, + QJsonObject &config) { QString stdOut; auto cbReadStdOut = [&](const QString &data, libssh::Client &) { @@ -454,19 +453,18 @@ ErrorCode ServerController::configureContainerWorker(const ServerCredentials &cr return ErrorCode::NoError; }; - ErrorCode e = runContainerScript(credentials, container, - replaceVars(amnezia::scriptData(ProtocolScriptType::configure_container, container), - genVarsForScript(credentials, container, config)), - cbReadStdOut, cbReadStdErr); - + replaceVars(amnezia::scriptData(ProtocolScriptType::configure_container, container), + genVarsForScript(credentials, container, config)), + cbReadStdOut, cbReadStdErr); m_configurator->updateContainerConfigAfterInstallation(container, config, stdOut); return e; } -ErrorCode ServerController::startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config) +ErrorCode ServerController::startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &config) { QString script = amnezia::scriptData(ProtocolScriptType::container_startup, container); @@ -475,16 +473,19 @@ ErrorCode ServerController::startupContainerWorker(const ServerCredentials &cred } ErrorCode e = uploadTextFileToContainer(container, credentials, - replaceVars(script, genVarsForScript(credentials, container, config)), - "/opt/amnezia/start.sh"); - if (e) return e; + replaceVars(script, genVarsForScript(credentials, container, config)), + "/opt/amnezia/start.sh"); + if (e) + return e; return runScript(credentials, - replaceVars("sudo docker exec -d $CONTAINER_NAME sh -c \"chmod a+x /opt/amnezia/start.sh && /opt/amnezia/start.sh\"", - genVarsForScript(credentials, container, config))); + replaceVars("sudo docker exec -d $CONTAINER_NAME sh -c \"chmod a+x /opt/amnezia/start.sh && " + "/opt/amnezia/start.sh\"", + genVarsForScript(credentials, container, config))); } -ServerController::Vars ServerController::genVarsForScript(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config) +ServerController::Vars ServerController::genVarsForScript(const ServerCredentials &credentials, + DockerContainer container, const QJsonObject &config) { const QJsonObject &openvpnConfig = config.value(ProtocolProps::protoToString(Proto::OpenVpn)).toObject(); const QJsonObject &cloakConfig = config.value(ProtocolProps::protoToString(Proto::Cloak)).toObject(); @@ -495,85 +496,102 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential Vars vars; - vars.append({{"$REMOTE_HOST", credentials.hostName}}); + vars.append({ { "$REMOTE_HOST", credentials.hostName } }); // OpenVPN vars - vars.append({{"$OPENVPN_SUBNET_IP", openvpnConfig.value(config_key::subnet_address).toString(protocols::openvpn::defaultSubnetAddress) }}); - vars.append({{"$OPENVPN_SUBNET_CIDR", openvpnConfig.value(config_key::subnet_cidr).toString(protocols::openvpn::defaultSubnetCidr) }}); - vars.append({{"$OPENVPN_SUBNET_MASK", openvpnConfig.value(config_key::subnet_mask).toString(protocols::openvpn::defaultSubnetMask) }}); + vars.append( + { { "$OPENVPN_SUBNET_IP", + openvpnConfig.value(config_key::subnet_address).toString(protocols::openvpn::defaultSubnetAddress) } }); + vars.append({ { "$OPENVPN_SUBNET_CIDR", + openvpnConfig.value(config_key::subnet_cidr).toString(protocols::openvpn::defaultSubnetCidr) } }); + vars.append({ { "$OPENVPN_SUBNET_MASK", + openvpnConfig.value(config_key::subnet_mask).toString(protocols::openvpn::defaultSubnetMask) } }); - vars.append({{"$OPENVPN_PORT", openvpnConfig.value(config_key::port).toString(protocols::openvpn::defaultPort) }}); - vars.append({{"$OPENVPN_TRANSPORT_PROTO", openvpnConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto) }}); + vars.append({ { "$OPENVPN_PORT", openvpnConfig.value(config_key::port).toString(protocols::openvpn::defaultPort) } }); + vars.append( + { { "$OPENVPN_TRANSPORT_PROTO", + openvpnConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto) } }); bool isNcpDisabled = openvpnConfig.value(config_key::ncp_disable).toBool(protocols::openvpn::defaultNcpDisable); - vars.append({{"$OPENVPN_NCP_DISABLE", isNcpDisabled ? protocols::openvpn::ncpDisableString : "" }}); + vars.append({ { "$OPENVPN_NCP_DISABLE", isNcpDisabled ? protocols::openvpn::ncpDisableString : "" } }); - vars.append({{"$OPENVPN_CIPHER", openvpnConfig.value(config_key::cipher).toString(protocols::openvpn::defaultCipher) }}); - vars.append({{"$OPENVPN_HASH", openvpnConfig.value(config_key::hash).toString(protocols::openvpn::defaultHash) }}); + vars.append({ { "$OPENVPN_CIPHER", + openvpnConfig.value(config_key::cipher).toString(protocols::openvpn::defaultCipher) } }); + vars.append({ { "$OPENVPN_HASH", openvpnConfig.value(config_key::hash).toString(protocols::openvpn::defaultHash) } }); bool isTlsAuth = openvpnConfig.value(config_key::tls_auth).toBool(protocols::openvpn::defaultTlsAuth); - vars.append({{"$OPENVPN_TLS_AUTH", isTlsAuth ? protocols::openvpn::tlsAuthString : "" }}); + vars.append({ { "$OPENVPN_TLS_AUTH", isTlsAuth ? protocols::openvpn::tlsAuthString : "" } }); if (!isTlsAuth) { // erase $OPENVPN_TA_KEY, so it will not set in OpenVpnConfigurator::genOpenVpnConfig - vars.append({{"$OPENVPN_TA_KEY", "" }}); + vars.append({ { "$OPENVPN_TA_KEY", "" } }); } - vars.append({{"$OPENVPN_ADDITIONAL_CLIENT_CONFIG", openvpnConfig.value(config_key::additional_client_config). - toString(protocols::openvpn::defaultAdditionalClientConfig) }}); - vars.append({{"$OPENVPN_ADDITIONAL_SERVER_CONFIG", openvpnConfig.value(config_key::additional_server_config). - toString(protocols::openvpn::defaultAdditionalServerConfig) }}); + vars.append({ { "$OPENVPN_ADDITIONAL_CLIENT_CONFIG", + openvpnConfig.value(config_key::additional_client_config) + .toString(protocols::openvpn::defaultAdditionalClientConfig) } }); + vars.append({ { "$OPENVPN_ADDITIONAL_SERVER_CONFIG", + openvpnConfig.value(config_key::additional_server_config) + .toString(protocols::openvpn::defaultAdditionalServerConfig) } }); // ShadowSocks vars - vars.append({{"$SHADOWSOCKS_SERVER_PORT", ssConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort) }}); - vars.append({{"$SHADOWSOCKS_LOCAL_PORT", ssConfig.value(config_key::local_port).toString(protocols::shadowsocks::defaultLocalProxyPort) }}); - vars.append({{"$SHADOWSOCKS_CIPHER", ssConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher) }}); + vars.append({ { "$SHADOWSOCKS_SERVER_PORT", + ssConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort) } }); + vars.append({ { "$SHADOWSOCKS_LOCAL_PORT", + ssConfig.value(config_key::local_port).toString(protocols::shadowsocks::defaultLocalProxyPort) } }); + vars.append({ { "$SHADOWSOCKS_CIPHER", + ssConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher) } }); - vars.append({{"$CONTAINER_NAME", ContainerProps::containerToString(container)}}); - vars.append({{"$DOCKERFILE_FOLDER", "/opt/amnezia/" + ContainerProps::containerToString(container)}}); + vars.append({ { "$CONTAINER_NAME", ContainerProps::containerToString(container) } }); + vars.append({ { "$DOCKERFILE_FOLDER", "/opt/amnezia/" + ContainerProps::containerToString(container) } }); // Cloak vars - vars.append({{"$CLOAK_SERVER_PORT", cloakConfig.value(config_key::port).toString(protocols::cloak::defaultPort) }}); - vars.append({{"$FAKE_WEB_SITE_ADDRESS", cloakConfig.value(config_key::site).toString(protocols::cloak::defaultRedirSite) }}); + vars.append({ { "$CLOAK_SERVER_PORT", cloakConfig.value(config_key::port).toString(protocols::cloak::defaultPort) } }); + vars.append({ { "$FAKE_WEB_SITE_ADDRESS", + cloakConfig.value(config_key::site).toString(protocols::cloak::defaultRedirSite) } }); // Wireguard vars - vars.append({{"$WIREGUARD_SUBNET_IP", wireguarConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress) }}); - vars.append({{"$WIREGUARD_SUBNET_CIDR", wireguarConfig.value(config_key::subnet_cidr).toString(protocols::wireguard::defaultSubnetCidr) }}); - vars.append({{"$WIREGUARD_SUBNET_MASK", wireguarConfig.value(config_key::subnet_mask).toString(protocols::wireguard::defaultSubnetMask) }}); + vars.append( + { { "$WIREGUARD_SUBNET_IP", + wireguarConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress) } }); + vars.append({ { "$WIREGUARD_SUBNET_CIDR", + wireguarConfig.value(config_key::subnet_cidr).toString(protocols::wireguard::defaultSubnetCidr) } }); + vars.append({ { "$WIREGUARD_SUBNET_MASK", + wireguarConfig.value(config_key::subnet_mask).toString(protocols::wireguard::defaultSubnetMask) } }); - vars.append({{"$WIREGUARD_SERVER_PORT", wireguarConfig.value(config_key::port).toString(protocols::wireguard::defaultPort) }}); + vars.append({ { "$WIREGUARD_SERVER_PORT", + wireguarConfig.value(config_key::port).toString(protocols::wireguard::defaultPort) } }); // IPsec vars - vars.append({{"$IPSEC_VPN_L2TP_NET", "192.168.42.0/24"}}); - vars.append({{"$IPSEC_VPN_L2TP_POOL", "192.168.42.10-192.168.42.250"}}); - vars.append({{"$IPSEC_VPN_L2TP_LOCAL", "192.168.42.1"}}); + vars.append({ { "$IPSEC_VPN_L2TP_NET", "192.168.42.0/24" } }); + vars.append({ { "$IPSEC_VPN_L2TP_POOL", "192.168.42.10-192.168.42.250" } }); + vars.append({ { "$IPSEC_VPN_L2TP_LOCAL", "192.168.42.1" } }); - vars.append({{"$IPSEC_VPN_XAUTH_NET", "192.168.43.0/24"}}); - vars.append({{"$IPSEC_VPN_XAUTH_POOL", "192.168.43.10-192.168.43.250"}}); + vars.append({ { "$IPSEC_VPN_XAUTH_NET", "192.168.43.0/24" } }); + vars.append({ { "$IPSEC_VPN_XAUTH_POOL", "192.168.43.10-192.168.43.250" } }); - vars.append({{"$IPSEC_VPN_SHA2_TRUNCBUG", "yes"}}); + vars.append({ { "$IPSEC_VPN_SHA2_TRUNCBUG", "yes" } }); - vars.append({{"$IPSEC_VPN_VPN_ANDROID_MTU_FIX", "yes"}}); - vars.append({{"$IPSEC_VPN_DISABLE_IKEV2", "no"}}); - vars.append({{"$IPSEC_VPN_DISABLE_L2TP", "no"}}); - vars.append({{"$IPSEC_VPN_DISABLE_XAUTH", "no"}}); + vars.append({ { "$IPSEC_VPN_VPN_ANDROID_MTU_FIX", "yes" } }); + vars.append({ { "$IPSEC_VPN_DISABLE_IKEV2", "no" } }); + vars.append({ { "$IPSEC_VPN_DISABLE_L2TP", "no" } }); + vars.append({ { "$IPSEC_VPN_DISABLE_XAUTH", "no" } }); - vars.append({{"$IPSEC_VPN_C2C_TRAFFIC", "no"}}); - - vars.append({{"$PRIMARY_SERVER_DNS", m_settings->primaryDns()}}); - vars.append({{"$SECONDARY_SERVER_DNS", m_settings->secondaryDns()}}); + vars.append({ { "$IPSEC_VPN_C2C_TRAFFIC", "no" } }); + vars.append({ { "$PRIMARY_SERVER_DNS", m_settings->primaryDns() } }); + vars.append({ { "$SECONDARY_SERVER_DNS", m_settings->secondaryDns() } }); // Sftp vars - vars.append({{"$SFTP_PORT", sftpConfig.value(config_key::port).toString(QString::number(ProtocolProps::defaultPort(Proto::Sftp))) }}); - vars.append({{"$SFTP_USER", sftpConfig.value(config_key::userName).toString() }}); - vars.append({{"$SFTP_PASSWORD", sftpConfig.value(config_key::password).toString() }}); - + vars.append( + { { "$SFTP_PORT", + sftpConfig.value(config_key::port).toString(QString::number(ProtocolProps::defaultPort(Proto::Sftp))) } }); + vars.append({ { "$SFTP_USER", sftpConfig.value(config_key::userName).toString() } }); + vars.append({ { "$SFTP_PASSWORD", sftpConfig.value(config_key::password).toString() } }); QString serverIp = Utils::getIPAddress(credentials.hostName); if (!serverIp.isEmpty()) { - vars.append({{"$SERVER_IP_ADDRESS", serverIp}}); - } - else { + vars.append({ { "$SERVER_IP_ADDRESS", serverIp } }); + } else { qWarning() << "ServerController::genVarsForScript unable to resolve address for credentials.hostName"; } @@ -592,10 +610,11 @@ QString ServerController::checkSshConnection(const ServerCredentials &credential return ErrorCode::NoError; }; - ErrorCode e = runScript(credentials, - amnezia::scriptData(SharedScriptType::check_connection), cbReadStdOut, cbReadStdErr); + ErrorCode e = + runScript(credentials, amnezia::scriptData(SharedScriptType::check_connection), cbReadStdOut, cbReadStdErr); - if (errorCode) *errorCode = e; + if (errorCode) + *errorCode = e; return stdOut; } @@ -607,23 +626,24 @@ void ServerController::setCancelInstallation(const bool cancel) ErrorCode ServerController::setupServerFirewall(const ServerCredentials &credentials) { - return runScript(credentials, - replaceVars(amnezia::scriptData(SharedScriptType::setup_host_firewall), - genVarsForScript(credentials))); + return runScript( + credentials, + replaceVars(amnezia::scriptData(SharedScriptType::setup_host_firewall), genVarsForScript(credentials))); } QString ServerController::replaceVars(const QString &script, const Vars &vars) { QString s = script; for (const QPair &var : vars) { - //qDebug() << "Replacing" << var.first << var.second; + // qDebug() << "Replacing" << var.first << var.second; s.replace(var.first, var.second); } - //qDebug().noquote() << script; + // qDebug().noquote() << script; return s; } -ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config) +ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &config) { if (container == DockerContainer::Dns) { return ErrorCode::NoError; @@ -646,8 +666,10 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential QStringList fixedPorts = ContainerProps::fixedPortsForContainer(container); QString defaultPort("%1"); - QString port = containerConfig.value(config_key::port).toString(defaultPort.arg(ProtocolProps::defaultPort(protocol))); - QString defaultTransportProto = ProtocolProps::transportProtoToString(ProtocolProps::defaultTransportProto(protocol), protocol); + QString port = + containerConfig.value(config_key::port).toString(defaultPort.arg(ProtocolProps::defaultPort(protocol))); + QString defaultTransportProto = + ProtocolProps::transportProtoToString(ProtocolProps::defaultTransportProto(protocol), protocol); QString transportProto = containerConfig.value(config_key::transport_proto).toString(defaultTransportProto); QString script = QString("sudo lsof -i -P -n | grep -E ':%1 ").arg(port); @@ -660,8 +682,8 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential script = script.append(" | grep LISTEN"); } - ErrorCode errorCode = runScript(credentials, - replaceVars(script, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr); + ErrorCode errorCode = runScript(credentials, replaceVars(script, genVarsForScript(credentials, container)), + cbReadStdOut, cbReadStdErr); if (errorCode != ErrorCode::NoError) { return errorCode; } @@ -689,9 +711,11 @@ ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, D }; const QString scriptData = amnezia::scriptData(SharedScriptType::check_user_in_sudo); - ErrorCode error = runScript(credentials, replaceVars(scriptData, genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr); + ErrorCode error = + runScript(credentials, replaceVars(scriptData, genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr); - if (!stdOut.contains("sudo")) return ErrorCode::ServerUserNotInSudo; + if (!stdOut.contains("sudo")) + return ErrorCode::ServerUserNotInSudo; return error; } @@ -718,7 +742,8 @@ ErrorCode ServerController::isServerDpkgBusy(const ServerCredentials &credential stdOut.clear(); runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::check_server_is_busy), - genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr); + genVarsForScript(credentials)), + cbReadStdOut, cbReadStdErr); if (!stdOut.isEmpty() || stdOut.contains("Unable to acquire the dpkg frontend lock")) { emit serverIsBusy(true); QThread::msleep(1000); @@ -738,7 +763,8 @@ ErrorCode ServerController::isServerDpkgBusy(const ServerCredentials &credential return future.result(); } -ErrorCode ServerController::getAlreadyInstalledContainers(const ServerCredentials &credentials, QMap &installedContainers) +ErrorCode ServerController::getAlreadyInstalledContainers(const ServerCredentials &credentials, + QMap &installedContainers) { QString stdOut; auto cbReadStdOut = [&](const QString &data, libssh::Client &) { @@ -770,13 +796,10 @@ ErrorCode ServerController::getAlreadyInstalledContainers(const ServerCredential QString transportProto = containerAndPortMatch.captured(3); DockerContainer container = ContainerProps::containerFromString(name); Proto mainProto = ContainerProps::defaultProtocol(container); - QJsonObject config { - { config_key::container, name }, - { ProtocolProps::protoToString(mainProto), QJsonObject { - { config_key::port, port }, - { config_key::transport_proto, transportProto }} - } - }; + QJsonObject config { { config_key::container, name }, + { ProtocolProps::protoToString(mainProto), + QJsonObject { { config_key::port, port }, + { config_key::transport_proto, transportProto } } } }; installedContainers.insert(container, config); } } @@ -784,7 +807,8 @@ ErrorCode ServerController::getAlreadyInstalledContainers(const ServerCredential return ErrorCode::NoError; } -ErrorCode ServerController::getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, const std::function &callback) +ErrorCode ServerController::getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, + const std::function &callback) { auto error = m_sshClient.getDecryptedPrivateKey(credentials, decryptedPrivateKey, callback); return error; diff --git a/client/core/servercontroller.h b/client/core/servercontroller.h index 70ac9cc2..cb74d571 100644 --- a/client/core/servercontroller.h +++ b/client/core/servercontroller.h @@ -4,8 +4,8 @@ #include #include -#include "defs.h" #include "containers/containers_defs.h" +#include "defs.h" #include "sshclient.h" class Settings; @@ -24,52 +24,61 @@ public: ErrorCode removeAllContainers(const ServerCredentials &credentials); ErrorCode removeContainer(const ServerCredentials &credentials, DockerContainer container); - ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, - QJsonObject &config, bool isUpdate = false); + ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config, + bool isUpdate = false); ErrorCode updateContainer(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &oldConfig, QJsonObject &newConfig); - ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials, QMap &installedContainers); + ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials, + QMap &installedContainers); - // create initial config - generate passwords, etc - QJsonObject createContainerInitialConfig(DockerContainer container, int port, TransportProto tp); - ErrorCode startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject()); + ErrorCode startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &config = QJsonObject()); - ErrorCode uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials, - const QString &file, const QString &path, - libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting); + ErrorCode uploadTextFileToContainer( + DockerContainer container, const ServerCredentials &credentials, const QString &file, const QString &path, + libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting); QByteArray getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials, const QString &path, ErrorCode *errorCode = nullptr); QString replaceVars(const QString &script, const Vars &vars); - Vars genVarsForScript(const ServerCredentials &credentials, DockerContainer container = DockerContainer::None, const QJsonObject &config = QJsonObject()); + Vars genVarsForScript(const ServerCredentials &credentials, DockerContainer container = DockerContainer::None, + const QJsonObject &config = QJsonObject()); ErrorCode runScript(const ServerCredentials &credentials, QString script, - const std::function &cbReadStdOut = nullptr, - const std::function &cbReadStdErr = nullptr); + const std::function &cbReadStdOut = nullptr, + const std::function &cbReadStdErr = nullptr); - ErrorCode runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script, - const std::function &cbReadStdOut = nullptr, - const std::function &cbReadStdErr = nullptr); + ErrorCode + runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script, + const std::function &cbReadStdOut = nullptr, + const std::function &cbReadStdErr = nullptr); QString checkSshConnection(const ServerCredentials &credentials, ErrorCode *errorCode = nullptr); void setCancelInstallation(const bool cancel); - ErrorCode getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, const std::function &callback); + ErrorCode getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, + const std::function &callback); + private: ErrorCode installDockerWorker(const ServerCredentials &credentials, DockerContainer container); - ErrorCode prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject()); - ErrorCode buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject()); + ErrorCode prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &config = QJsonObject()); + ErrorCode buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &config = QJsonObject()); ErrorCode runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config); - ErrorCode configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config); + ErrorCode configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, + QJsonObject &config); - ErrorCode isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config); - bool isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig); + ErrorCode isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &config); + bool isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, + const QJsonObject &newConfig); ErrorCode isUserInSudo(const ServerCredentials &credentials, DockerContainer container); ErrorCode isServerDpkgBusy(const ServerCredentials &credentials, DockerContainer container); - - ErrorCode uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, - const QString &remotePath, libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting); + + ErrorCode uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath, + libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting); ErrorCode setupServerFirewall(const ServerCredentials &credentials); diff --git a/client/resources.qrc b/client/resources.qrc index df6fcb3e..3435a107 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -274,5 +274,6 @@ ui/qml/Pages2/PageProtocolShadowSocksSettings.qml ui/qml/Pages2/PageProtocolCloakSettings.qml ui/qml/Pages2/PageProtocolRaw.qml + ui/qml/Pages2/PageSettingsLogging.qml diff --git a/client/secure_qsettings.cpp b/client/secure_qsettings.cpp index e71eff48..1df10168 100644 --- a/client/secure_qsettings.cpp +++ b/client/secure_qsettings.cpp @@ -1,30 +1,28 @@ #include "secure_qsettings.h" #include "platforms/ios/MobileUtils.h" +#include "QAead.h" +#include "QBlockCipher.h" +#include "utilities.h" #include #include #include #include #include #include +#include #include #include -#include "utilities.h" -#include -#include "QAead.h" -#include "QBlockCipher.h" using namespace QKeychain; SecureQSettings::SecureQSettings(const QString &organization, const QString &application, QObject *parent) - : QObject{parent}, - m_settings(organization, application, parent), - encryptedKeys({"Servers/serversList"}) + : QObject { parent }, m_settings(organization, application, parent), encryptedKeys({ "Servers/serversList" }) { bool encrypted = m_settings.value("Conf/encrypted").toBool(); // convert settings to encrypted for if updated to >= 2.1.0 - if (encryptionRequired() && ! encrypted) { + if (encryptionRequired() && !encrypted) { for (const QString &key : m_settings.allKeys()) { if (encryptedKeys.contains(key)) { const QVariant &val = value(key); @@ -44,15 +42,15 @@ QVariant SecureQSettings::value(const QString &key, const QVariant &defaultValue return m_cache.value(key); } - if (!m_settings.contains(key)) return defaultValue; + if (!m_settings.contains(key)) + return defaultValue; QVariant retVal; // check if value is not encrypted, v. < 2.0.x retVal = m_settings.value(key); if (retVal.isValid()) { - if (retVal.userType() == QVariant::ByteArray && - retVal.toByteArray().mid(0, magicString.size()) == magicString) { + if (retVal.userType() == QVariant::ByteArray && retVal.toByteArray().mid(0, magicString.size()) == magicString) { if (getEncKey().isEmpty() || getEncIv().isEmpty()) { qCritical() << "SecureQSettings::setValue Decryption requested, but key is empty"; @@ -71,8 +69,7 @@ QVariant SecureQSettings::value(const QString &key, const QVariant &defaultValue retVal = QVariant(); } } - } - else { + } else { qWarning() << "SecureQSettings::value invalid QVariant value"; retVal = QVariant(); } @@ -95,14 +92,12 @@ void SecureQSettings::setValue(const QString &key, const QVariant &value) QByteArray encryptedValue = encryptText(decryptedValue); m_settings.setValue(key, magicString + encryptedValue); - } - else { + } else { qCritical() << "SecureQSettings::setValue Encryption required, but key is empty"; return; } - } - else { + } else { m_settings.setValue(key, value); } @@ -139,7 +134,8 @@ QByteArray SecureQSettings::backupAppConfig() const bool SecureQSettings::restoreAppConfig(const QByteArray &json) { QJsonObject cfg = QJsonDocument::fromJson(json).object(); - if (cfg.isEmpty()) return false; + if (cfg.isEmpty()) + return false; for (const QString &key : cfg.keys()) { setValue(key, cfg.value(key).toVariant()); @@ -149,14 +145,13 @@ bool SecureQSettings::restoreAppConfig(const QByteArray &json) return true; } - -QByteArray SecureQSettings::encryptText(const QByteArray& value) const +QByteArray SecureQSettings::encryptText(const QByteArray &value) const { QSimpleCrypto::QBlockCipher cipher; return cipher.encryptAesBlockCipher(value, getEncKey(), getEncIv()); } -QByteArray SecureQSettings::decryptText(const QByteArray& ba) const +QByteArray SecureQSettings::decryptText(const QByteArray &ba) const { QSimpleCrypto::QBlockCipher cipher; return cipher.decryptAesBlockCipher(ba, getEncKey(), getEncIv()); @@ -228,13 +223,11 @@ QByteArray SecureQSettings::getSecTag(const QString &tag) job->setAutoDelete(false); job->setKey(tag); QEventLoop loop; - job->connect(job.data(), &ReadPasswordJob::finished, job.data(), [&loop](){ - loop.quit(); - }); + job->connect(job.data(), &ReadPasswordJob::finished, job.data(), [&loop]() { loop.quit(); }); job->start(); loop.exec(); - if ( job->error() ) { + if (job->error()) { qCritical() << "SecureQSettings::getSecTag Error:" << job->errorString(); } @@ -249,9 +242,7 @@ void SecureQSettings::setSecTag(const QString &tag, const QByteArray &data) job->setBinaryData(data); QEventLoop loop; QTimer::singleShot(1000, &loop, SLOT(quit())); - job->connect(job.data(), &WritePasswordJob::finished, job.data(), [&loop](){ - loop.quit(); - }); + job->connect(job.data(), &WritePasswordJob::finished, job.data(), [&loop]() { loop.quit(); }); job->start(); loop.exec(); @@ -260,4 +251,10 @@ void SecureQSettings::setSecTag(const QString &tag, const QByteArray &data) } } - +void SecureQSettings::clearSettings() +{ + QMutexLocker locker(&mutex); + m_settings.clear(); + m_cache.clear(); + sync(); +} diff --git a/client/secure_qsettings.h b/client/secure_qsettings.h index 9b1f6167..7421ce01 100644 --- a/client/secure_qsettings.h +++ b/client/secure_qsettings.h @@ -1,23 +1,22 @@ #ifndef SECUREQSETTINGS_H #define SECUREQSETTINGS_H -#include -#include #include #include +#include +#include #include "keychain.h" - -constexpr const char* settingsKeyTag = "settingsKeyTag"; -constexpr const char* settingsIvTag = "settingsIvTag"; -constexpr const char* keyChainName = "AmneziaVPN-Keychain"; - +constexpr const char *settingsKeyTag = "settingsKeyTag"; +constexpr const char *settingsIvTag = "settingsIvTag"; +constexpr const char *keyChainName = "AmneziaVPN-Keychain"; class SecureQSettings : public QObject { public: - explicit SecureQSettings(const QString &organization, const QString &application = QString(), QObject *parent = nullptr); + explicit SecureQSettings(const QString &organization, const QString &application = QString(), + QObject *parent = nullptr); QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const; void setValue(const QString &key, const QVariant &value); @@ -28,7 +27,7 @@ public: bool restoreAppConfig(const QByteArray &json); QByteArray encryptText(const QByteArray &value) const; - QByteArray decryptText(const QByteArray& ba) const; + QByteArray decryptText(const QByteArray &ba) const; bool encryptionRequired() const; @@ -38,6 +37,8 @@ public: static QByteArray getSecTag(const QString &tag); static void setSecTag(const QString &tag, const QByteArray &data); + void clearSettings(); + private: QSettings m_settings; diff --git a/client/settings.cpp b/client/settings.cpp index 1781cb2e..fbdd63ce 100644 --- a/client/settings.cpp +++ b/client/settings.cpp @@ -1,6 +1,6 @@ -#include "version.h" #include "settings.h" #include "utilities.h" +#include "version.h" #include "containers/containers_defs.h" #include "logger.h" @@ -8,10 +8,7 @@ const char Settings::cloudFlareNs1[] = "1.1.1.1"; const char Settings::cloudFlareNs2[] = "1.0.0.1"; - -Settings::Settings(QObject* parent) : - QObject(parent), - m_settings(ORGANIZATION_NAME, APPLICATION_NAME, this) +Settings::Settings(QObject *parent) : QObject(parent), m_settings(ORGANIZATION_NAME, APPLICATION_NAME, this) { // Import old settings if (serversCount() == 0) { @@ -20,7 +17,7 @@ Settings::Settings(QObject* parent) : QString serverName = m_settings.value("Server/serverName").toString(); int port = m_settings.value("Server/serverPort").toInt(); - if (!user.isEmpty() && !password.isEmpty() && !serverName.isEmpty()){ + if (!user.isEmpty() && !password.isEmpty() && !serverName.isEmpty()) { QJsonObject server; server.insert(config_key::userName, user); server.insert(config_key::password, password); @@ -46,7 +43,8 @@ int Settings::serversCount() const QJsonObject Settings::server(int index) const { const QJsonArray &servers = serversArray(); - if (index >= servers.size()) return QJsonObject(); + if (index >= servers.size()) + return QJsonObject(); return servers.at(index).toObject(); } @@ -61,7 +59,8 @@ void Settings::addServer(const QJsonObject &server) void Settings::removeServer(int index) { QJsonArray servers = serversArray(); - if (index >= servers.size()) return; + if (index >= servers.size()) + return; servers.removeAt(index); setServersArray(servers); @@ -70,7 +69,8 @@ void Settings::removeServer(int index) bool Settings::editServer(int index, const QJsonObject &server) { QJsonArray servers = serversArray(); - if (index >= servers.size()) return false; + if (index >= servers.size()) + return false; servers.replace(index, server); setServersArray(servers); @@ -94,8 +94,8 @@ QString Settings::defaultContainerName(int serverIndex) const QString name = server(serverIndex).value(config_key::defaultContainer).toString(); if (name.isEmpty()) { return ContainerProps::containerToString(DockerContainer::None); - } - else return name; + } else + return name; } QMap Settings::containers(int serverIndex) const @@ -104,7 +104,8 @@ QMap Settings::containers(int serverIndex) const QMap containersMap; for (const QJsonValue &val : containers) { - containersMap.insert(ContainerProps::containerFromString(val.toObject().value(config_key::container).toString()), val.toObject()); + containersMap.insert(ContainerProps::containerFromString(val.toObject().value(config_key::container).toString()), + val.toObject()); } return containersMap; @@ -114,17 +115,17 @@ void Settings::setContainers(int serverIndex, const QMap &sites) const QString &site = i.key(); const QString &ip = i.value(); - if (allSites.contains(site) && allSites.value(site) == ip) continue; + if (allSites.contains(site) && allSites.value(site) == ip) + continue; allSites.insert(site, ip); } @@ -263,8 +264,7 @@ QStringList Settings::getVpnIps(RouteMode mode) const for (auto i = m.constBegin(); i != m.constEnd(); ++i) { if (Utils::checkIpSubnetFormat(i.key())) { ips.append(i.key()); - } - else if (Utils::checkIpSubnetFormat(i.value().toString())) { + } else if (Utils::checkIpSubnetFormat(i.value().toString())) { ips.append(i.value().toString()); } } @@ -275,7 +275,8 @@ QStringList Settings::getVpnIps(RouteMode mode) const void Settings::removeVpnSite(RouteMode mode, const QString &site) { QVariantMap sites = vpnSites(mode); - if (!sites.contains(site)) return; + if (!sites.contains(site)) + return; sites.remove(site); setVpnSites(mode, sites); @@ -285,7 +286,8 @@ void Settings::addVpnIps(RouteMode mode, const QStringList &ips) { QVariantMap sites = vpnSites(mode); for (const QString &ip : ips) { - if (ip.isEmpty()) continue; + if (ip.isEmpty()) + continue; sites.insert(ip, ""); } @@ -297,7 +299,8 @@ void Settings::removeVpnSites(RouteMode mode, const QStringList &sites) { QVariantMap sitesMap = vpnSites(mode); for (const QString &site : sites) { - if (site.isEmpty()) continue; + if (site.isEmpty()) + continue; sitesMap.remove(site); } @@ -305,9 +308,20 @@ void Settings::removeVpnSites(RouteMode mode, const QStringList &sites) setVpnSites(mode, sitesMap); } -QString Settings::primaryDns() const { return m_settings.value("Conf/primaryDns", cloudFlareNs1).toString(); } +QString Settings::primaryDns() const +{ + return m_settings.value("Conf/primaryDns", cloudFlareNs1).toString(); +} -QString Settings::secondaryDns() const { return m_settings.value("Conf/secondaryDns", cloudFlareNs2).toString(); } +QString Settings::secondaryDns() const +{ + return m_settings.value("Conf/secondaryDns", cloudFlareNs2).toString(); +} + +void Settings::clearSettings() +{ + m_settings.clearSettings(); +} ServerCredentials Settings::defaultServerCredentials() const { diff --git a/client/settings.h b/client/settings.h index 9bf40ac7..00b02c15 100644 --- a/client/settings.h +++ b/client/settings.h @@ -183,6 +183,8 @@ public: m_settings.setValue("Conf/appLanguage", locale); }; + void clearSettings(); + signals: void saveLogsChanged(); diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 6fc5f4e9..2259721a 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -16,12 +16,19 @@ InstallController::InstallController(const QSharedPointer &servers void InstallController::install(DockerContainer container, int port, TransportProto transportProto) { Proto mainProto = ContainerProps::defaultProtocol(container); + QJsonObject containerConfig; - QJsonObject containerConfig { { config_key::port, QString::number(port) }, - { config_key::transport_proto, - ProtocolProps::transportProtoToString(transportProto, mainProto) } }; - QJsonObject config { { config_key::container, ContainerProps::containerToString(container) }, - { ProtocolProps::protoToString(mainProto), containerConfig } }; + containerConfig.insert(config_key::port, QString::number(port)); + containerConfig.insert(config_key::transport_proto, ProtocolProps::transportProtoToString(transportProto, mainProto)); + + if (container == DockerContainer::Sftp) { + containerConfig.insert(config_key::userName, protocols::sftp::defaultUserName); + containerConfig.insert(config_key::password, Utils::getRandomString(10)); + } + + QJsonObject config; + config.insert(config_key::container, ContainerProps::containerToString(container)); + config.insert(ProtocolProps::protoToString(mainProto), containerConfig); if (m_shouldCreateServer) { if (isServerAlreadyExists()) { diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 05b8fbf9..a0b9753b 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -27,6 +27,7 @@ namespace PageLoader PageSettingsApplication, PageSettingsBackup, PageSettingsAbout, + PageSettingsLogging, PageSetupWizardStart, PageSetupWizardCredentials, diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index 42dd2231..b501d085 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -46,14 +46,15 @@ void SettingsController::setSecondaryDns(const QString &dns) emit secondaryDnsChanged(); } -bool SettingsController::isSaveLogsEnabled() +bool SettingsController::isLoggingEnable() { return m_settings->isSaveLogs(); } -void SettingsController::setSaveLogs(bool enable) +void SettingsController::toggleLogging(bool enable) { m_settings->setSaveLogs(enable); + emit loggingStateChanged(); } void SettingsController::openLogsFolder() @@ -101,3 +102,8 @@ QString SettingsController::getAppVersion() { return m_appVersion; } + +void SettingsController::clearSettings() +{ + m_settings->clearSettings(); +} diff --git a/client/ui/controllers/settingsController.h b/client/ui/controllers/settingsController.h index f961a37d..313f934d 100644 --- a/client/ui/controllers/settingsController.h +++ b/client/ui/controllers/settingsController.h @@ -16,6 +16,7 @@ public: Q_PROPERTY(QString primaryDns READ getPrimaryDns WRITE setPrimaryDns NOTIFY primaryDnsChanged) Q_PROPERTY(QString secondaryDns READ getSecondaryDns WRITE setSecondaryDns NOTIFY secondaryDnsChanged) + Q_PROPERTY(bool isLoggingEnable READ isLoggingEnable WRITE toggleLogging NOTIFY loggingStateChanged) public slots: void setAmneziaDns(bool enable); @@ -27,8 +28,8 @@ public slots: QString getSecondaryDns(); void setSecondaryDns(const QString &dns); - bool isSaveLogsEnabled(); - void setSaveLogs(bool enable); + bool isLoggingEnable(); + void toggleLogging(bool enable); void openLogsFolder(); void exportLogsFile(); @@ -39,9 +40,12 @@ public slots: QString getAppVersion(); + void clearSettings(); + signals: void primaryDnsChanged(); void secondaryDnsChanged(); + void loggingStateChanged(); private: QSharedPointer m_serversModel; diff --git a/client/ui/models/languageModel.cpp b/client/ui/models/languageModel.cpp index 76eeb37d..adbbdaaa 100644 --- a/client/ui/models/languageModel.cpp +++ b/client/ui/models/languageModel.cpp @@ -54,3 +54,8 @@ int LanguageModel::getCurrentLanguageIndex() default: return static_cast(LanguageSettings::AvailableLanguageEnum::English); break; } } + +QString LanguageModel::getCurrentLanuageName() +{ + return m_availableLanguages[getCurrentLanguageIndex()].name; +} diff --git a/client/ui/models/languageModel.h b/client/ui/models/languageModel.h index b3ff4f6e..4e8a9092 100644 --- a/client/ui/models/languageModel.h +++ b/client/ui/models/languageModel.h @@ -46,6 +46,7 @@ public: public slots: void changeLanguage(const LanguageSettings::AvailableLanguageEnum language); int getCurrentLanguageIndex(); + QString getCurrentLanuageName(); signals: void updateTranslations(const QLocale &locale); diff --git a/client/ui/pages_logic/ServerContainersLogic.cpp b/client/ui/pages_logic/ServerContainersLogic.cpp index a2971cf8..89eeb6c8 100644 --- a/client/ui/pages_logic/ServerContainersLogic.cpp +++ b/client/ui/pages_logic/ServerContainersLogic.cpp @@ -89,7 +89,7 @@ void ServerContainersLogic::onPushButtonRemoveClicked(DockerContainer container) void ServerContainersLogic::onPushButtonContinueClicked(DockerContainer c, int port, TransportProto tp) { ServerController serverController(m_settings); - QJsonObject config = serverController.createContainerInitialConfig(c, port, tp); + QJsonObject config; // = serverController.createContainerInitialConfig(c, port, tp); emit uiLogic()->goToPage(Page::ServerConfiguringProgress); qApp->processEvents(); diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml index 27ead16c..c925f801 100644 --- a/client/ui/qml/Controls2/LabelWithButtonType.qml +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -17,14 +17,16 @@ Item { property string textColor: "#d7d8db" - implicitWidth: content.implicitWidth - implicitHeight: content.implicitHeight + implicitWidth: content.implicitWidth + content.anchors.topMargin + content.anchors.bottomMargin + implicitHeight: content.implicitHeight + content.anchors.leftMargin + content.anchors.rightMargin RowLayout { id: content anchors.fill: parent anchors.leftMargin: 16 anchors.rightMargin: 16 + anchors.topMargin: 16 + anchors.bottomMargin: 16 Rectangle { id: leftImageBackground @@ -56,8 +58,6 @@ Item { color: root.textColor Layout.fillWidth: true - Layout.topMargin: 16 - Layout.bottomMargin: description.visible ? 0 : 16 horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter @@ -72,7 +72,6 @@ Item { visible: root.descriptionText !== "" Layout.fillWidth: true - Layout.bottomMargin: 16 horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index b378a6c8..54d315b0 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -48,6 +48,7 @@ PageType { Layout.topMargin: 16 text: qsTr("Language") + descriptionText: LanguageModel.getCurrentLanuageName() rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { @@ -60,6 +61,22 @@ PageType { } + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Logging") + descriptionText: SettingsController.isLoggingEnable ? qsTr("Enabled") : qsTr("Disabled") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + goToPage(PageEnum.PageSettingsLogging) + } + } + + DividerType {} + LabelWithButtonType { Layout.fillWidth: true @@ -67,10 +84,27 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { + questionDrawer.headerText = qsTr("Reset settings and remove all data from the application?") + questionDrawer.descriptionText = qsTr("All settings will be reset to default. All installed AmneziaVPN services will still remain on the server.") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + SettingsController.clearSettings() + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true } } DividerType {} + + QuestionDrawer { + id: questionDrawer + } } } } diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml index ddf9f2ba..1c196aa3 100644 --- a/client/ui/qml/Pages2/PageSettingsBackup.qml +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -44,96 +44,6 @@ PageType { headerText: qsTr("Backup") } - SwitcherType { - Layout.fillWidth: true - Layout.topMargin: 16 - - text: qsTr("Save logs") - - checked: SettingsController.isSaveLogsEnabled() - onCheckedChanged: { - if (checked !== SettingsController.isSaveLogsEnabled()) { - SettingsController.setSaveLogs(checked) - } - } - } - - RowLayout { - Layout.fillWidth: true - - ColumnLayout { - Layout.alignment: Qt.AlignBaseline - Layout.preferredWidth: root.width / 3 - - ImageButtonType { - Layout.alignment: Qt.AlignHCenter - - implicitWidth: 56 - implicitHeight: 56 - - image: "qrc:/images/controls/folder-open.svg" - - onClicked: SettingsController.openLogsFolder() - } - - CaptionTextType { - horizontalAlignment: Text.AlignHCenter - Layout.fillWidth: true - - text: qsTr("Open folder with logs") - color: "#D7D8DB" - } - } - - ColumnLayout { - Layout.alignment: Qt.AlignBaseline - Layout.preferredWidth: root.width / 3 - - ImageButtonType { - Layout.alignment: Qt.AlignHCenter - - implicitWidth: 56 - implicitHeight: 56 - - image: "qrc:/images/controls/save.svg" - - onClicked: SettingsController.exportLogsFile() - } - - CaptionTextType { - horizontalAlignment: Text.AlignHCenter - Layout.fillWidth: true - - text: qsTr("Save logs to file") - color: "#D7D8DB" - } - } - - ColumnLayout { - Layout.alignment: Qt.AlignBaseline - Layout.preferredWidth: root.width / 3 - - ImageButtonType { - Layout.alignment: Qt.AlignHCenter - - implicitWidth: 56 - implicitHeight: 56 - - image: "qrc:/images/controls/delete.svg" - - onClicked: SettingsController.clearLogs() - } - - CaptionTextType { - horizontalAlignment: Text.AlignHCenter - Layout.fillWidth: true - - text: qsTr("Clear logs") - color: "#D7D8DB" - } - } - } - ListItemTitleType { Layout.fillWidth: true Layout.topMargin: 10 diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml new file mode 100644 index 00000000..998065e4 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -0,0 +1,138 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Config" +import "../Controls2/TextTypes" + +PageType { + id: root + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 20 + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + spacing: 16 + + HeaderType { + Layout.fillWidth: true + + headerText: qsTr("Logging") + } + + SwitcherType { + Layout.fillWidth: true + Layout.topMargin: 16 + + text: qsTr("Save logs") + + checked: SettingsController.isLoggingEnable + onCheckedChanged: { + if (checked !== SettingsController.isLoggingEnable) { + SettingsController.isLoggingEnable = checked + } + } + } + + RowLayout { + Layout.fillWidth: true + + ColumnLayout { + Layout.alignment: Qt.AlignBaseline + Layout.preferredWidth: root.width / 3 + + ImageButtonType { + Layout.alignment: Qt.AlignHCenter + + implicitWidth: 56 + implicitHeight: 56 + + image: "qrc:/images/controls/folder-open.svg" + + onClicked: SettingsController.openLogsFolder() + } + + CaptionTextType { + horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + + text: qsTr("Open folder with logs") + color: "#D7D8DB" + } + } + + ColumnLayout { + Layout.alignment: Qt.AlignBaseline + Layout.preferredWidth: root.width / 3 + + ImageButtonType { + Layout.alignment: Qt.AlignHCenter + + implicitWidth: 56 + implicitHeight: 56 + + image: "qrc:/images/controls/save.svg" + + onClicked: SettingsController.exportLogsFile() + } + + CaptionTextType { + horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + + text: qsTr("Save logs to file") + color: "#D7D8DB" + } + } + + ColumnLayout { + Layout.alignment: Qt.AlignBaseline + Layout.preferredWidth: root.width / 3 + + ImageButtonType { + Layout.alignment: Qt.AlignHCenter + + implicitWidth: 56 + implicitHeight: 56 + + image: "qrc:/images/controls/delete.svg" + + onClicked: SettingsController.clearLogs() + } + + CaptionTextType { + horizontalAlignment: Text.AlignHCenter + Layout.fillWidth: true + + text: qsTr("Clear logs") + color: "#D7D8DB" + } + } + } + } + } +} From 5d677a9115724ded952bd61434777866d4993330 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 18 Jul 2023 11:15:04 +0900 Subject: [PATCH 045/278] added pages for sftp and tor website settings --- client/CMakeLists.txt | 12 +- client/amnezia_application.cpp | 3 + client/amnezia_application.h | 3 + client/images/controls/copy.svg | 4 + client/protocols/protocols_defs.h | 1 + client/resources.qrc | 3 + client/ui/controllers/installController.cpp | 79 +++++ client/ui/controllers/installController.h | 5 + client/ui/controllers/pageController.h | 3 + client/ui/models/containers_model.cpp | 5 + client/ui/models/containers_model.h | 1 + client/ui/models/servers_model.cpp | 5 + client/ui/models/servers_model.h | 2 + client/ui/models/services/sftpConfigModel.cpp | 64 ++++ client/ui/models/services/sftpConfigModel.h | 39 +++ .../Components/SettingsContainersListView.qml | 11 + .../ui/qml/Controls2/LabelWithButtonType.qml | 24 +- .../Controls2/TextTypes/CaptionTextType.qml | 2 +- .../Controls2/TextTypes/ListItemTitleType.qml | 2 +- .../Pages2/PageProtocolOpenVpnSettings.qml | 2 + .../ui/qml/Pages2/PageServiceSftpSettings.qml | 280 ++++++++++++++++++ .../Pages2/PageServiceTorWebsiteSettings.qml | 169 +++++++++++ 22 files changed, 713 insertions(+), 6 deletions(-) create mode 100644 client/images/controls/copy.svg create mode 100644 client/ui/models/services/sftpConfigModel.cpp create mode 100644 client/ui/models/services/sftpConfigModel.h create mode 100644 client/ui/qml/Pages2/PageServiceSftpSettings.qml create mode 100644 client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index be1c3b92..8f1c9353 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -129,8 +129,16 @@ file(GLOB_RECURSE PAGE_LOGIC_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/ file(GLOB CONFIGURATORS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/configurators/*.h) file(GLOB CONFIGURATORS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/configurators/*.cpp) -file(GLOB UI_MODELS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/models/*.h ${CMAKE_CURRENT_LIST_DIR}/ui/models/protocols/*.h) -file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/models/*.cpp ${CMAKE_CURRENT_LIST_DIR}/ui/models/protocols/*.cpp) +file(GLOB UI_MODELS_H CONFIGURE_DEPENDS + ${CMAKE_CURRENT_LIST_DIR}/ui/models/*.h + ${CMAKE_CURRENT_LIST_DIR}/ui/models/protocols/*.h + ${CMAKE_CURRENT_LIST_DIR}/ui/models/services/*.h +) +file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS + ${CMAKE_CURRENT_LIST_DIR}/ui/models/*.cpp + ${CMAKE_CURRENT_LIST_DIR}/ui/models/protocols/*.cpp + ${CMAKE_CURRENT_LIST_DIR}/ui/models/services/*.cpp +) file(GLOB UI_CONTROLLERS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.h) file(GLOB UI_CONTROLLERS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.cpp) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index abd839ed..7bf035fa 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -267,6 +267,9 @@ void AmneziaApplication::initModels() m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this)); m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get()); #endif + + m_sftpConfigModel.reset(new SftpConfigModel(this)); + m_engine->rootContext()->setContextProperty("SftpConfigModel", m_sftpConfigModel.get()); } void AmneziaApplication::initControllers() diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 6e9fcdf0..3ba42e41 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -31,6 +31,7 @@ #include "ui/models/protocols/wireguardConfigModel.h" #include "ui/models/protocols_model.h" #include "ui/models/servers_model.h" +#include "ui/models/services/sftpConfigModel.h" #define amnApp (static_cast(QCoreApplication::instance())) @@ -91,6 +92,8 @@ private: QScopedPointer m_ikev2ConfigModel; #endif + QScopedPointer m_sftpConfigModel; + QSharedPointer m_vpnConnection; QScopedPointer m_notificationHandler; diff --git a/client/images/controls/copy.svg b/client/images/controls/copy.svg new file mode 100644 index 00000000..787e71db --- /dev/null +++ b/client/images/controls/copy.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/protocols/protocols_defs.h b/client/protocols/protocols_defs.h index 73c2abdf..2fba272f 100644 --- a/client/protocols/protocols_defs.h +++ b/client/protocols/protocols_defs.h @@ -65,6 +65,7 @@ namespace amnezia constexpr char wireguard[] = "wireguard"; constexpr char shadowsocks[] = "shadowsocks"; constexpr char cloak[] = "cloak"; + constexpr char sftp[] = "sftp"; } diff --git a/client/resources.qrc b/client/resources.qrc index 3435a107..e1afa294 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -275,5 +275,8 @@ ui/qml/Pages2/PageProtocolCloakSettings.qml ui/qml/Pages2/PageProtocolRaw.qml ui/qml/Pages2/PageSettingsLogging.qml + ui/qml/Pages2/PageServiceSftpSettings.qml + images/controls/copy.svg + ui/qml/Pages2/PageServiceTorWebsiteSettings.qml diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 2259721a..84633b54 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -1,6 +1,8 @@ #include "installController.h" +#include #include +#include #include "core/errorstrings.h" #include "core/servercontroller.h" @@ -214,3 +216,80 @@ void InstallController::setShouldCreateServer(bool shouldCreateServer) { m_shouldCreateServer = shouldCreateServer; } + +void InstallController::mountSftpDrive(const QString &port, const QString &password, const QString &username) +{ + QString mountPath; + QString cmd; + + int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + ServerCredentials serverCredentials = + qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); + QString hostname = serverCredentials.hostName; + +#ifdef Q_OS_WINDOWS + mountPath = getNextDriverLetter() + ":"; + // QString cmd = QString("net use \\\\sshfs\\%1@x.x.x.x!%2 /USER:%1 %3") + // .arg(labelTftpUserNameText()) + // .arg(labelTftpPortText()) + // .arg(labelTftpPasswordText()); + + cmd = "C:\\Program Files\\SSHFS-Win\\bin\\sshfs.exe"; +#elif defined AMNEZIA_DESKTOP + mountPath = + QString("%1/sftp:%2:%3").arg(QStandardPaths::writableLocation(QStandardPaths::HomeLocation), hostname, port); + QDir dir(mountPath); + if (!dir.exists()) { + dir.mkpath(mountPath); + } + + cmd = "/usr/local/bin/sshfs"; +#endif + +#ifdef AMNEZIA_DESKTOP + QSharedPointer process; + process.reset(new QProcess()); + m_sftpMountProcesses.append(process); + process->setProcessChannelMode(QProcess::MergedChannels); + + connect(process.get(), &QProcess::readyRead, this, [this, process, mountPath]() { + QString s = process->readAll(); + if (s.contains("The service sshfs has been started")) { + QDesktopServices::openUrl(QUrl("file:///" + mountPath)); + } + qDebug() << s; + }); + + process->setProgram(cmd); + + QString args = QString("%1@%2:/ %3 " + "-o port=%4 " + "-f " + "-o reconnect " + "-o rellinks " + "-o fstypename=SSHFS " + "-o ssh_command=/usr/bin/ssh.exe " + "-o UserKnownHostsFile=/dev/null " + "-o StrictHostKeyChecking=no " + "-o password_stdin") + .arg(username, hostname, mountPath, port); + + // args.replace("\n", " "); + // args.replace("\r", " "); + // #ifndef Q_OS_WIN + // args.replace("reconnect-orellinks", ""); + // #endif + process->setArguments(args.split(" ", Qt::SkipEmptyParts)); + process->start(); + process->waitForStarted(50); + if (process->state() != QProcess::Running) { + qDebug() << "onPushButtonSftpMountDriveClicked process not started"; + qDebug() << args; + } else { + process->write((password + "\n").toUtf8()); + } + + // qDebug().noquote() << "onPushButtonSftpMountDriveClicked" << args; + +#endif +} diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index 75f4fdef..8458b46d 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -2,6 +2,7 @@ #define INSTALLCONTROLLER_H #include +#include #include "containers/containers_defs.h" #include "core/defs.h" @@ -28,6 +29,8 @@ public slots: QRegularExpression ipAddressPortRegExp(); + void mountSftpDrive(const QString &port, const QString &password, const QString &username); + signals: void installContainerFinished(bool isInstalledContainerFound); void installServerFinished(bool isInstalledContainerFound); @@ -52,6 +55,8 @@ private: ServerCredentials m_currentlyInstalledServerCredentials; bool m_shouldCreateServer; + + QList> m_sftpMountProcesses; }; #endif // INSTALLCONTROLLER_H diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index a0b9753b..d79b100a 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -29,6 +29,9 @@ namespace PageLoader PageSettingsAbout, PageSettingsLogging, + PageServiceSftpSettings, + PageServiceTorWebsiteSettings, + PageSetupWizardStart, PageSetupWizardCredentials, PageSetupWizardProtocols, diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index 7b6ffaee..922542dd 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -118,6 +118,11 @@ QString ContainersModel::getCurrentlyProcessedContainerName() return ContainerProps::containerHumanNames().value(static_cast(m_currentlyProcessedContainerIndex)); } +QJsonObject ContainersModel::getCurrentlyProcessedContainerConfig() +{ + return qvariant_cast(data(index(m_currentlyProcessedContainerIndex), ConfigRole)); +} + void ContainersModel::removeAllContainers() { diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index b79b058a..75ba9114 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -50,6 +50,7 @@ public slots: int getCurrentlyProcessedContainerIndex(); QString getCurrentlyProcessedContainerName(); + QJsonObject getCurrentlyProcessedContainerConfig(); void removeAllContainers(); void removeCurrentlyProcessedContainer(); diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 6fad9af6..fabbb829 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -106,6 +106,11 @@ int ServersModel::getCurrentlyProcessedServerIndex() return m_currentlyProcessedServerIndex; } +QString ServersModel::getCurrentlyProcessedServerHostName() +{ + return qvariant_cast(data(m_currentlyProcessedServerIndex, HostNameRole)); +} + bool ServersModel::isDefaultServerCurrentlyProcessed() { return m_defaultServerIndex == m_currentlyProcessedServerIndex; diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index f149d85b..a31e3c04 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -45,6 +45,8 @@ public slots: void setCurrentlyProcessedServerIndex(const int index); int getCurrentlyProcessedServerIndex(); + QString getCurrentlyProcessedServerHostName(); + void addServer(const QJsonObject &server); void removeServer(); diff --git a/client/ui/models/services/sftpConfigModel.cpp b/client/ui/models/services/sftpConfigModel.cpp new file mode 100644 index 00000000..3cbb5ebc --- /dev/null +++ b/client/ui/models/services/sftpConfigModel.cpp @@ -0,0 +1,64 @@ +#include "sftpConfigModel.h" + +#include "protocols/protocols_defs.h" + +SftpConfigModel::SftpConfigModel(QObject *parent) : QAbstractListModel(parent) +{ +} + +int SftpConfigModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 1; +} + +QVariant SftpConfigModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { + return false; + } + + switch (role) { + case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(); + case Roles::UserNameRole: + return m_protocolConfig.value(config_key::userName).toString(protocols::sftp::defaultUserName); + case Roles::PasswordRole: return m_protocolConfig.value(config_key::password).toString(); + } + + return QVariant(); +} + +void SftpConfigModel::updateModel(const QJsonObject &config) +{ + beginResetModel(); + m_container = ContainerProps::containerFromString(config.value(config_key::container).toString()); + + m_fullConfig = config; + QJsonObject protocolConfig = config.value(config_key::sftp).toObject(); + + m_protocolConfig.insert(config_key::userName, + protocolConfig.value(config_key::userName).toString(protocols::sftp::defaultUserName)); + + m_protocolConfig.insert(config_key::password, protocolConfig.value(config_key::password).toString()); + + m_protocolConfig.insert(config_key::port, protocolConfig.value(config_key::port).toString()); + + endResetModel(); +} + +QJsonObject SftpConfigModel::getConfig() +{ + m_fullConfig.insert(config_key::sftp, m_protocolConfig); + return m_fullConfig; +} + +QHash SftpConfigModel::roleNames() const +{ + QHash roles; + + roles[PortRole] = "port"; + roles[UserNameRole] = "username"; + roles[PasswordRole] = "password"; + + return roles; +} diff --git a/client/ui/models/services/sftpConfigModel.h b/client/ui/models/services/sftpConfigModel.h new file mode 100644 index 00000000..e948591e --- /dev/null +++ b/client/ui/models/services/sftpConfigModel.h @@ -0,0 +1,39 @@ +#ifndef SFTPCONFIGMODEL_H +#define SFTPCONFIGMODEL_H + +#include +#include + +#include "containers/containers_defs.h" + +class SftpConfigModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + PortRole = Qt::UserRole + 1, + UserNameRole, + PasswordRole + }; + + explicit SftpConfigModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +public slots: + void updateModel(const QJsonObject &config); + QJsonObject getConfig(); + +protected: + QHash roleNames() const override; + +private: + DockerContainer m_container; + QJsonObject m_protocolConfig; + QJsonObject m_fullConfig; +}; + +#endif // SFTPCONFIGMODEL_H diff --git a/client/ui/qml/Components/SettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml index d2f3ee81..eac473f4 100644 --- a/client/ui/qml/Components/SettingsContainersListView.qml +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -92,6 +92,7 @@ ListView { if (isInstalled) { var containerIndex = root.model.mapToSource(index) ContainersModel.setCurrentlyProcessedContainerIndex(containerIndex) + if (config[ContainerProps.containerTypeToString(containerIndex)]["isThirdPartyConfig"]) { ProtocolsModel.updateModel(config) goToPage(PageEnum.PageProtocolRaw) @@ -114,6 +115,16 @@ ListView { goToPage(PageEnum.PageProtocolIKev2Settings) break } + case ContainerEnum.Sftp: { + SftpConfigModel.updateModel(config) + goToPage(PageEnum.PageServiceSftpSettings) + break + } + case ContainerEnum.TorWebSite: { + goToPage(PageEnum.PageServiceTorWebsiteSettings) + break + } + default: { if (serviceType !== ProtocolEnum.Other) { //todo disable settings for dns container ProtocolsModel.updateModel(config) diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml index c925f801..3aca1d57 100644 --- a/client/ui/qml/Controls2/LabelWithButtonType.qml +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -16,6 +16,10 @@ Item { property string leftImageSource property string textColor: "#d7d8db" + property string descriptionColor: "#878B91" + property string rightImageColor: "#878B91" + + property bool descriptionOnTop: false implicitWidth: content.implicitWidth + content.anchors.topMargin + content.anchors.bottomMargin implicitHeight: content.implicitHeight + content.anchors.leftMargin + content.anchors.rightMargin @@ -53,26 +57,41 @@ Item { } ColumnLayout { + property real textLineHeight: 21.6 + property real descriptionTextLineHeight: 16 + + property int textPixelSize: 18 + property int descriptionTextSize: 13 + ListItemTitleType { text: root.text - color: root.textColor + color: root.descriptionOnTop ? root.descriptionColor : root.textColor Layout.fillWidth: true + lineHeight: root.descriptionOnTop ? parent.descriptionTextLineHeight : parent.textLineHeight + font.pixelSize: root.descriptionOnTop ? parent.descriptionTextSize : parent.textPixelSize + font.letterSpacing: root.descriptionOnTop ? 0.02 : 0 + horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter } + CaptionTextType { id: description - color: "#878B91" + color: root.descriptionOnTop ? root.textColor : root.descriptionColor text: root.descriptionText visible: root.descriptionText !== "" Layout.fillWidth: true + lineHeight: root.descriptionOnTop ? parent.textLineHeight : parent.descriptionTextLineHeight + font.pixelSize: root.descriptionOnTop ? parent.textPixelSize : parent.descriptionTextSize + font.letterSpacing: root.descriptionOnTop ? 0 : 0.02 + horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter } @@ -83,6 +102,7 @@ Item { hoverEnabled: false image: rightImageSource + imageColor: rightImageColor visible: rightImageSource ? true : false Layout.alignment: Qt.AlignRight diff --git a/client/ui/qml/Controls2/TextTypes/CaptionTextType.qml b/client/ui/qml/Controls2/TextTypes/CaptionTextType.qml index b9e41da2..f7acb6dd 100644 --- a/client/ui/qml/Controls2/TextTypes/CaptionTextType.qml +++ b/client/ui/qml/Controls2/TextTypes/CaptionTextType.qml @@ -10,5 +10,5 @@ Text { font.family: "PT Root UI VF" font.letterSpacing: 0.02 - wrapMode: Text.WordWrap + wrapMode: Text.Wrap } diff --git a/client/ui/qml/Controls2/TextTypes/ListItemTitleType.qml b/client/ui/qml/Controls2/TextTypes/ListItemTitleType.qml index 30bd7900..917e65de 100644 --- a/client/ui/qml/Controls2/TextTypes/ListItemTitleType.qml +++ b/client/ui/qml/Controls2/TextTypes/ListItemTitleType.qml @@ -9,5 +9,5 @@ Text { font.weight: 400 font.family: "PT Root UI VF" - wrapMode: Text.WordWrap + wrapMode: Text.Wrap } diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index 0bad68e9..028f9fd3 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -413,6 +413,8 @@ PageType { BasicButtonType { Layout.topMargin: 24 + Layout.leftMargin: -8 + implicitHeight: 32 defaultColor: "transparent" hoveredColor: Qt.rgba(1, 1, 1, 0.08) diff --git a/client/ui/qml/Pages2/PageServiceSftpSettings.qml b/client/ui/qml/Pages2/PageServiceSftpSettings.qml new file mode 100644 index 00000000..ffa42859 --- /dev/null +++ b/client/ui/qml/Pages2/PageServiceSftpSettings.qml @@ -0,0 +1,280 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + //todo move to main? + Connections { + target: InstallController + + function onInstallationErrorOccurred(errorMessage) { + PageController.showErrorMessage(errorMessage) + } + + function onUpdateContainerFinished() { + //todo change to notification + PageController.showErrorMessage(qsTr("Settings updated successfully")) + } + } + + ColumnLayout { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + } + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.implicitHeight + + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + enabled: ServersModel.isCurrentlyProcessedServerHasWriteAccess() + + ListView { + id: listview + + width: parent.width + height: listview.contentItem.height + + clip: true + interactive: false + + model: SftpConfigModel + + delegate: Item { + implicitWidth: listview.width + implicitHeight: col.implicitHeight + + ColumnLayout { + id: col + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + spacing: 0 + + HeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: qsTr("SFTP settings") + } + + LabelWithButtonType { + Layout.fillWidth: true + Layout.topMargin: 32 + + text: qsTr("Host") + descriptionText: ServersModel.getCurrentlyProcessedServerHostName() + + descriptionOnTop: true + + rightImageSource: "qrc:/images/controls/copy.svg" + rightImageColor: "#D7D8DB" + + clickedFunction: function() { + col.copyToClipBoard(descriptionText) + } + } + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Port") + descriptionText: port + + descriptionOnTop: true + + rightImageSource: "qrc:/images/controls/copy.svg" + rightImageColor: "#D7D8DB" + + clickedFunction: function() { + col.copyToClipBoard(descriptionText) + } + } + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Login") + descriptionText: username + + descriptionOnTop: true + + rightImageSource: "qrc:/images/controls/copy.svg" + rightImageColor: "#D7D8DB" + + clickedFunction: function() { + col.copyToClipBoard(descriptionText) + } + } + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Password") + descriptionText: password + + descriptionOnTop: true + + rightImageSource: "qrc:/images/controls/copy.svg" + rightImageColor: "#D7D8DB" + + clickedFunction: function() { + col.copyToClipBoard(descriptionText) + } + } + + TextEdit{ + id: clipboard + visible: false + } + + function copyToClipBoard(text) { + clipboard.text = text + clipboard.selectAll() + clipboard.copy() + clipboard.select(0, 0) + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.bottomMargin: 24 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + 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("Mount folder on device") + + onClicked: { + PageController.showBusyIndicator(true) + InstallController.mountSftpDrive(port, password, username) + PageController.showBusyIndicator(false) + } + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + readonly property string windowsFirstLink: "WinFsp" + readonly property string windowsSecondLink: "SSHFS-Win" + + readonly property string macosFirstLink: "macFUSE" + readonly property string macosSecondLink: "SSHFS" + + onLinkActivated: Qt.openUrlExternally(link) + textFormat: Text.RichText + text: { + var str = qsTr("In order to mount remote SFTP folder as local drive, perform following steps:
") + if (Qt.platform.os === "windows") { + str += qsTr("
1. Install the latest version of ") + windowsFirstLink + "\n" + str += qsTr("
2. Install the latest version of ") + windowsSecondLink + "\n" + } else if (Qt.platform.os === "osx") { + str += qsTr("
1. Install the latest version of ") + macosFirstLink + "\n" + str += qsTr("
2. Install the latest version of ") + macosSecondLink + "\n" + } else if (Qt.platform.os === "linux") { + return "" + } else return "" + + return str + } + } + + BasicButtonType { + Layout.topMargin: 16 + Layout.bottomMargin: 16 + Layout.leftMargin: 8 + implicitHeight: 32 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + disabledColor: "#878B91" + textColor: "#FBB26A" + + text: qsTr("Detailed instructions") + + onClicked: { +// Qt.openUrlExternally("https://github.com/amnezia-vpn/desktop-client/releases/latest") + } + } + + BasicButtonType { + Layout.topMargin: 24 + Layout.bottomMargin: 16 + Layout.leftMargin: 8 + implicitHeight: 32 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + textColor: "#EB5757" + + text: qsTr("Remove SFTP and all data stored there") + + onClicked: { + questionDrawer.headerText = qsTr("Some description") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + goToPage(PageEnum.PageDeinstalling) + ContainersModel.removeCurrentlyProcessedContainer() + closePage() + closePage() //todo auto close to deinstall page? + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true + } + } + } + } + } + } + + QuestionDrawer { + id: questionDrawer + } + } +} diff --git a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml new file mode 100644 index 00000000..a47a95e3 --- /dev/null +++ b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml @@ -0,0 +1,169 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ContainerProps 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + //todo move to main? + Connections { + target: InstallController + + function onInstallationErrorOccurred(errorMessage) { + PageController.showErrorMessage(errorMessage) + } + + function onUpdateContainerFinished() { + //todo change to notification + PageController.showErrorMessage(qsTr("Settings updated successfully")) + } + } + + ColumnLayout { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + } + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.implicitHeight + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + spacing: 0 + + HeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: qsTr("Tor website settings") + } + + LabelWithButtonType { + Layout.fillWidth: true + Layout.topMargin: 32 + + text: qsTr("Website address") + descriptionText: { + var config = ContainersModel.getCurrentlyProcessedContainerConfig() + var containerIndex = ContainersModel.getCurrentlyProcessedContainerIndex() + return config[ContainerProps.containerTypeToString(containerIndex)]["site"] + } + + descriptionOnTop: true + textColor: "#FBB26A" + + rightImageSource: "qrc:/images/controls/copy.svg" + rightImageColor: "#D7D8DB" + + clickedFunction: function() { + content.copyToClipBoard(descriptionText) + } + } + + TextEdit{ + id: clipboard + visible: false + } + + function copyToClipBoard(text) { + clipboard.text = text + clipboard.selectAll() + clipboard.copy() + clipboard.select(0, 0) + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 40 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + onLinkActivated: Qt.openUrlExternally(link) + textFormat: Text.RichText + text: qsTr("Use Tor Browser to open this url.") + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("After installation it takes several minutes while your onion site will become available in the Tor Network.") + } + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("When configuring WordPress set the domain as this onion address.") + } + + BasicButtonType { + Layout.topMargin: 24 + Layout.bottomMargin: 16 + Layout.leftMargin: 8 + implicitHeight: 32 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + textColor: "#EB5757" + + text: qsTr("Remove website") + + onClicked: { + questionDrawer.headerText = qsTr("Some description") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + goToPage(PageEnum.PageDeinstalling) + ContainersModel.removeCurrentlyProcessedContainer() + closePage() + closePage() //todo auto close to deinstall page? + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true + } + } + } + + QuestionDrawer { + id: questionDrawer + } + } +} From 0a1359ed16e16b9452e07528f07eaabfb295bb15 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 24 Jul 2023 16:31:04 +0900 Subject: [PATCH 046/278] moved the platform-specific android code for the new ui --- client/CMakeLists.txt | 2 + client/amnezia_application.cpp | 35 ++- .../platforms/android/android_controller.cpp | 278 ++++++++--------- client/platforms/android/android_controller.h | 26 +- .../platforms/android/authResultReceiver.cpp | 16 + client/platforms/android/authResultReceiver.h | 32 ++ client/protocols/wireguardprotocol.cpp | 55 ++-- client/resources.qrc | 2 +- client/ui/controllers/exportController.cpp | 65 +++- client/ui/controllers/exportController.h | 12 + client/ui/controllers/importController.cpp | 54 +++- client/ui/controllers/importController.h | 12 +- client/ui/controllers/installController.cpp | 39 +++ client/ui/controllers/installController.h | 1 + client/ui/controllers/pageController.cpp | 27 +- client/ui/controllers/pageController.h | 13 +- client/ui/pages_logic/ServerSettingsLogic.cpp | 68 ++--- client/ui/pages_logic/StartPageLogic.cpp | 108 +++---- .../qml/Components/ShareConnectionDrawer.qml | 4 +- client/ui/qml/Controls2/CheckBoxType.qml | 46 +-- client/ui/qml/Controls2/PageType.qml | 1 - .../ui/qml/Controls2/VerticalRadioButton.qml | 62 ++-- client/ui/qml/Pages2/PageSettingsAbout.qml | 6 +- .../Pages2/PageSetupWizardConfigSource.qml | 20 +- .../ui/qml/Pages2/PageSetupWizardQrReader.qml | 52 ++++ client/ui/qml/Pages2/PageSetupWizardStart.qml | 4 + client/ui/qml/Pages2/PageShare.qml | 39 ++- client/ui/qml/Pages2/PageStart.qml | 12 + client/ui/qml/Pages2/PageTest.qml | 282 ------------------ client/ui/qml/main2.qml | 14 +- client/ui/uilogic.cpp | 231 +++++++------- 31 files changed, 854 insertions(+), 764 deletions(-) create mode 100644 client/platforms/android/authResultReceiver.cpp create mode 100644 client/platforms/android/authResultReceiver.h create mode 100644 client/ui/qml/Pages2/PageSetupWizardQrReader.qml delete mode 100644 client/ui/qml/Pages2/PageTest.qml diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 0ef72ed0..936dcbf2 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -293,6 +293,7 @@ if(ANDROID) ${CMAKE_CURRENT_LIST_DIR}/platforms/android/android_notificationhandler.h ${CMAKE_CURRENT_LIST_DIR}/platforms/android/androidutils.h ${CMAKE_CURRENT_LIST_DIR}/platforms/android/androidvpnactivity.h + ${CMAKE_CURRENT_LIST_DIR}/platforms/android/authResultReceiver.h ${CMAKE_CURRENT_LIST_DIR}/protocols/android_vpnprotocol.h ) @@ -301,6 +302,7 @@ if(ANDROID) ${CMAKE_CURRENT_LIST_DIR}/platforms/android/android_notificationhandler.cpp ${CMAKE_CURRENT_LIST_DIR}/platforms/android/androidutils.cpp ${CMAKE_CURRENT_LIST_DIR}/platforms/android/androidvpnactivity.cpp + ${CMAKE_CURRENT_LIST_DIR}/platforms/android/authResultReceiver.cpp ${CMAKE_CURRENT_LIST_DIR}/protocols/android_vpnprotocol.cpp ) endif() diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 53d8fe30..2ee9af16 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -14,9 +15,12 @@ #include "version.h" #include "platforms/ios/QRCodeReaderBase.h" +#if defined(Q_OS_ANDROID) + #include "platforms/android/android_controller.h" +#endif -#include "ui/pages.h" #include "protocols/qml_register_protocols.h" +#include "ui/pages.h" #if defined(Q_OS_IOS) #include "platforms/ios/QtAppDelegate-C-Interface.h" @@ -33,7 +37,7 @@ AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecond setQuitOnLastWindowClosed(false); // Fix config file permissions -#ifdef Q_OS_LINUX && !defined(Q_OS_ANDROID) +#if defined Q_OS_LINUX && !defined(Q_OS_ANDROID) { QSettings s(ORGANIZATION_NAME, APPLICATION_NAME); s.setValue("permFixed", true); @@ -87,16 +91,35 @@ void AmneziaApplication::init() initModels(); initControllers(); +#ifdef Q_OS_ANDROID + connect(AndroidController::instance(), &AndroidController::initialized, this, + [this](bool status, bool connected, const QDateTime &connectionDate) { + if (connected) { + m_connectionController->onConnectionStateChanged(Vpn::ConnectionState::Connected); + if (m_vpnConnection) + m_vpnConnection->restoreConnection(); + } + }); + if (!AndroidController::instance()->initialize()) { + qCritical() << QString("Init failed"); + if (m_vpnConnection) + emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Error); + return; + } + + connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, m_importController.get(), + &ImportController::extractConfigFromData); + connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, m_pageController.get(), + &PageController::goToPageViewConfig); +#endif + m_notificationHandler.reset(NotificationHandler::create(nullptr)); connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(), &NotificationHandler::setConnectionState); - void openConnection(); - void closeConnection(); - connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(), - &PageController::raise); + &PageController::raiseMainWindow); connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(), &ConnectionController::openConnection); connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(), diff --git a/client/platforms/android/android_controller.cpp b/client/platforms/android/android_controller.cpp index 18955532..a56edcbf 100644 --- a/client/platforms/android/android_controller.cpp +++ b/client/platforms/android/android_controller.cpp @@ -13,17 +13,16 @@ #include "android_controller.h" #include "private/qandroidextras_p.h" -#include "ui/pages_logic/StartPageLogic.h" -#include "androidvpnactivity.h" #include "androidutils.h" +#include "androidvpnactivity.h" -namespace { -AndroidController* s_instance = nullptr; +namespace +{ + AndroidController *s_instance = nullptr; -constexpr auto PERMISSIONHELPER_CLASS = - "org/amnezia/vpn/qt/VPNPermissionHelper"; -} // namespace + constexpr auto PERMISSIONHELPER_CLASS = "org/amnezia/vpn/qt/VPNPermissionHelper"; +} // namespace AndroidController::AndroidController() : QObject() { @@ -33,109 +32,127 @@ AndroidController::AndroidController() : QObject() auto activity = AndroidVPNActivity::instance(); - connect(activity, &AndroidVPNActivity::serviceConnected, this, []() { - qDebug() << "Transact: service connected"; - AndroidVPNActivity::sendToService(ServiceAction::ACTION_REQUEST_STATISTIC, ""); - }, Qt::QueuedConnection); + connect( + activity, &AndroidVPNActivity::serviceConnected, this, + []() { + qDebug() << "Transact: service connected"; + AndroidVPNActivity::sendToService(ServiceAction::ACTION_REQUEST_STATISTIC, ""); + }, + Qt::QueuedConnection); - connect(activity, &AndroidVPNActivity::eventInitialized, this, - [this](const QString& parcelBody) { - // We might get multiple Init events as widgets, or fragments - // might query this. - if (m_init) { - return; - } + connect( + activity, &AndroidVPNActivity::eventInitialized, this, + [this](const QString &parcelBody) { + // We might get multiple Init events as widgets, or fragments + // might query this. + if (m_init) { + return; + } - qDebug() << "Transact: init"; + qDebug() << "Transact: init"; - m_init = true; + m_init = true; - auto doc = QJsonDocument::fromJson(parcelBody.toUtf8()); - qlonglong time = doc.object()["time"].toVariant().toLongLong(); + auto doc = QJsonDocument::fromJson(parcelBody.toUtf8()); + qlonglong time = doc.object()["time"].toVariant().toLongLong(); - isConnected = doc.object()["connected"].toBool(); + isConnected = doc.object()["connected"].toBool(); - if (isConnected) { - emit scheduleStatusCheckSignal(); - } + if (isConnected) { + emit scheduleStatusCheckSignal(); + } - emit initialized( - true, isConnected, - time > 0 ? QDateTime::fromMSecsSinceEpoch(time) : QDateTime()); + emit initialized(true, isConnected, time > 0 ? QDateTime::fromMSecsSinceEpoch(time) : QDateTime()); - setFallbackConnectedNotification(); - }, Qt::QueuedConnection); + setFallbackConnectedNotification(); + }, + Qt::QueuedConnection); - connect(activity, &AndroidVPNActivity::eventConnected, this, - [this](const QString& parcelBody) { - Q_UNUSED(parcelBody); - qDebug() << "Transact: connected"; + connect( + activity, &AndroidVPNActivity::eventConnected, this, + [this](const QString &parcelBody) { + Q_UNUSED(parcelBody); + qDebug() << "Transact: connected"; - if (!isConnected) { - emit scheduleStatusCheckSignal(); - } + if (!isConnected) { + emit scheduleStatusCheckSignal(); + } - isConnected = true; + isConnected = true; - emit connectionStateChanged(VpnProtocol::Connected); - }, Qt::QueuedConnection); + emit connectionStateChanged(Vpn::ConnectionState::Connected); + }, + Qt::QueuedConnection); - connect(activity, &AndroidVPNActivity::eventDisconnected, this, - [this]() { - qDebug() << "Transact: disconnected"; + connect( + activity, &AndroidVPNActivity::eventDisconnected, this, + [this]() { + qDebug() << "Transact: disconnected"; - isConnected = false; + isConnected = false; - emit connectionStateChanged(VpnProtocol::Disconnected); - }, Qt::QueuedConnection); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); + }, + Qt::QueuedConnection); - connect(activity, &AndroidVPNActivity::eventStatisticUpdate, this, - [this](const QString& parcelBody) { - auto doc = QJsonDocument::fromJson(parcelBody.toUtf8()); + connect( + activity, &AndroidVPNActivity::eventStatisticUpdate, this, + [this](const QString &parcelBody) { + auto doc = QJsonDocument::fromJson(parcelBody.toUtf8()); - QString rx = doc.object()["rx_bytes"].toString(); - QString tx = doc.object()["tx_bytes"].toString(); - QString endpoint = doc.object()["endpoint"].toString(); - QString deviceIPv4 = doc.object()["deviceIpv4"].toString(); + QString rx = doc.object()["rx_bytes"].toString(); + QString tx = doc.object()["tx_bytes"].toString(); + QString endpoint = doc.object()["endpoint"].toString(); + QString deviceIPv4 = doc.object()["deviceIpv4"].toString(); - emit statusUpdated(rx, tx, endpoint, deviceIPv4); - }, Qt::QueuedConnection); + emit statusUpdated(rx, tx, endpoint, deviceIPv4); + }, + Qt::QueuedConnection); - connect(activity, &AndroidVPNActivity::eventBackendLogs, this, - [this](const QString& parcelBody) { - qDebug() << "Transact: backend logs"; + connect( + activity, &AndroidVPNActivity::eventBackendLogs, this, + [this](const QString &parcelBody) { + qDebug() << "Transact: backend logs"; - QString buffer = parcelBody.toUtf8(); - if (m_logCallback) { - m_logCallback(buffer); - } - }, Qt::QueuedConnection); + QString buffer = parcelBody.toUtf8(); + if (m_logCallback) { + m_logCallback(buffer); + } + }, + Qt::QueuedConnection); - connect(activity, &AndroidVPNActivity::eventActivationError, this, - [this](const QString& parcelBody) { - Q_UNUSED(parcelBody) - qDebug() << "Transact: error"; - emit connectionStateChanged(VpnProtocol::Error); - }, Qt::QueuedConnection); + connect( + activity, &AndroidVPNActivity::eventActivationError, this, + [this](const QString &parcelBody) { + Q_UNUSED(parcelBody) + qDebug() << "Transact: error"; + emit connectionStateChanged(Vpn::ConnectionState::Error); + }, + Qt::QueuedConnection); - connect(activity, &AndroidVPNActivity::eventConfigImport, this, - [this](const QString& parcelBody) { - qDebug() << "Transact: config import"; - auto doc = QJsonDocument::fromJson(parcelBody.toUtf8()); + connect( + activity, &AndroidVPNActivity::eventConfigImport, this, + [this](const QString &parcelBody) { + qDebug() << "Transact: config import"; + auto doc = QJsonDocument::fromJson(parcelBody.toUtf8()); - QString buffer = doc.object()["config"].toString(); - qDebug() << "Transact: config string" << buffer; - importConfig(buffer); - }, Qt::QueuedConnection); + QString buffer = doc.object()["config"].toString(); + qDebug() << "Transact: config string" << buffer; + importConfigFromOutside(buffer); + }, + Qt::QueuedConnection); - connect(activity, &AndroidVPNActivity::serviceDisconnected, this, - [this]() { - qDebug() << "Transact: service disconnected"; - m_serviceConnected = false; - }, Qt::QueuedConnection); + connect( + activity, &AndroidVPNActivity::serviceDisconnected, this, + [this]() { + qDebug() << "Transact: service disconnected"; + m_serviceConnected = false; + }, + Qt::QueuedConnection); } -AndroidController* AndroidController::instance() { +AndroidController *AndroidController::instance() +{ if (!s_instance) { s_instance = new AndroidController(); } @@ -143,16 +160,13 @@ AndroidController* AndroidController::instance() { return s_instance; } -bool AndroidController::initialize(StartPageLogic *startPageLogic) +bool AndroidController::initialize() { qDebug() << "Initializing"; - m_startPageLogic = startPageLogic; - // Hook in the native implementation for startActivityForResult into the JNI - JNINativeMethod methods[]{{"startActivityForResult", - "(Landroid/content/Intent;)V", - reinterpret_cast(startActivityForResult)}}; + JNINativeMethod methods[] { { "startActivityForResult", "(Landroid/content/Intent;)V", + reinterpret_cast(startActivityForResult) } }; QJniObject javaClass(PERMISSIONHELPER_CLASS); QJniEnvironment env; jclass objectClass = env->GetObjectClass(javaClass.object()); @@ -168,11 +182,9 @@ ErrorCode AndroidController::start() { qDebug() << "Prompting for VPN permission"; QJniObject activity = AndroidUtils::getActivity(); - auto appContext = activity.callObjectMethod( - "getApplicationContext", "()Landroid/content/Context;"); - QJniObject::callStaticMethod( - PERMISSIONHELPER_CLASS, "startService", "(Landroid/content/Context;)V", - appContext.object()); + auto appContext = activity.callObjectMethod("getApplicationContext", "()Landroid/content/Context;"); + QJniObject::callStaticMethod(PERMISSIONHELPER_CLASS, "startService", "(Landroid/content/Context;)V", + appContext.object()); QJsonDocument doc(m_vpnConfig); AndroidVPNActivity::sendToService(ServiceAction::ACTION_ACTIVATE, doc.toJson()); @@ -180,7 +192,8 @@ ErrorCode AndroidController::start() return NoError; } -void AndroidController::stop() { +void AndroidController::stop() +{ qDebug() << "AndroidController::stop"; AndroidVPNActivity::sendToService(ServiceAction::ACTION_DEACTIVATE, QString()); @@ -188,16 +201,16 @@ void AndroidController::stop() { // Activates the tunnel that is currently set // in the VPN Service -void AndroidController::resumeStart() { +void AndroidController::resumeStart() +{ AndroidVPNActivity::sendToService(ServiceAction::ACTION_RESUME_ACTIVATE, QString()); } /* * Sets the current notification text that is shown */ -void AndroidController::setNotificationText(const QString& title, - const QString& message, - int timerSec) { +void AndroidController::setNotificationText(const QString &title, const QString &message, int timerSec) +{ QJsonObject args; args["title"] = title; args["message"] = message; @@ -207,7 +220,8 @@ void AndroidController::setNotificationText(const QString& title, AndroidVPNActivity::sendToService(ServiceAction::ACTION_SET_NOTIFICATION_TEXT, doc.toJson()); } -void AndroidController::shareConfig(const QString& configContent, const QString& suggestedName) { +void AndroidController::shareConfig(const QString &configContent, const QString &suggestedName) +{ AndroidVPNActivity::saveFileAs(configContent, suggestedName); } @@ -216,7 +230,8 @@ void AndroidController::shareConfig(const QString& configContent, const QString& * switches into the Connected state without the app open * e.g via always-on vpn */ -void AndroidController::setFallbackConnectedNotification() { +void AndroidController::setFallbackConnectedNotification() +{ QJsonObject args; args["title"] = tr("AmneziaVPN"); //% "Ready for you to connect" @@ -227,11 +242,13 @@ void AndroidController::setFallbackConnectedNotification() { AndroidVPNActivity::sendToService(ServiceAction::ACTION_SET_NOTIFICATION_FALLBACK, doc.toJson()); } -void AndroidController::checkStatus() { +void AndroidController::checkStatus() +{ AndroidVPNActivity::sendToService(ServiceAction::ACTION_REQUEST_STATISTIC, QString()); } -void AndroidController::getBackendLogs(std::function&& a_callback) { +void AndroidController::getBackendLogs(std::function &&a_callback) +{ qDebug() << "get logs"; m_logCallback = std::move(a_callback); @@ -239,16 +256,13 @@ void AndroidController::getBackendLogs(std::function&& a_c AndroidVPNActivity::sendToService(ServiceAction::ACTION_REQUEST_GET_LOG, QString()); } -void AndroidController::cleanupBackendLogs() { +void AndroidController::cleanupBackendLogs() +{ qDebug() << "cleanup logs"; AndroidVPNActivity::sendToService(ServiceAction::ACTION_REQUEST_CLEANUP_LOG, QString()); } -void AndroidController::importConfig(const QString& data){ - m_startPageLogic->importAnyFile(data); -} - const QJsonObject &AndroidController::vpnConfig() const { return m_vpnConfig; @@ -285,31 +299,29 @@ void AndroidController::startActivityForResult(JNIEnv *env, jobject, jobject int qDebug() << "start vpnPermissionHelper"; Q_UNUSED(env); - QtAndroidPrivate::startActivity(intent, 1337, - [](int receiverRequestCode, int resultCode, - const QJniObject& data) { - // Currently this function just used in - // VPNService.kt::checkPermissions. So the result - // we're getting is if the User gave us the - // Vpn.bind permission. In case of NO we should - // abort. - Q_UNUSED(receiverRequestCode); - Q_UNUSED(data); + QtAndroidPrivate::startActivity(intent, 1337, [](int receiverRequestCode, int resultCode, const QJniObject &data) { + // Currently this function just used in + // VPNService.kt::checkPermissions. So the result + // we're getting is if the User gave us the + // Vpn.bind permission. In case of NO we should + // abort. + Q_UNUSED(receiverRequestCode); + Q_UNUSED(data); - AndroidController* controller = AndroidController::instance(); - if (!controller) { - return; - } + AndroidController *controller = AndroidController::instance(); + if (!controller) { + return; + } - if (resultCode == ACTIVITY_RESULT_OK) { - qDebug() << "VPN PROMPT RESULT - Accepted"; - controller->resumeStart(); - return; - } - // If the request got rejected abort the current - // connection. - qWarning() << "VPN PROMPT RESULT - Rejected"; - emit controller->connectionStateChanged(VpnProtocol::Disconnected); - }); + if (resultCode == ACTIVITY_RESULT_OK) { + qDebug() << "VPN PROMPT RESULT - Accepted"; + controller->resumeStart(); + return; + } + // If the request got rejected abort the current + // connection. + qWarning() << "VPN PROMPT RESULT - Rejected"; + emit controller->connectionStateChanged(Vpn::ConnectionState::Disconnected); + }); return; } diff --git a/client/platforms/android/android_controller.h b/client/platforms/android/android_controller.h index 7e5b52c8..baa0bc80 100644 --- a/client/platforms/android/android_controller.h +++ b/client/platforms/android/android_controller.h @@ -8,36 +8,32 @@ #include #include -#include "ui/pages_logic/StartPageLogic.h" - #include "protocols/vpnprotocol.h" using namespace amnezia; - class AndroidController : public QObject { Q_OBJECT public: explicit AndroidController(); - static AndroidController* instance(); + static AndroidController *instance(); virtual ~AndroidController() override = default; - bool initialize(StartPageLogic *startPageLogic); + bool initialize(); ErrorCode start(); void stop(); void resumeStart(); void checkStatus(); - void setNotificationText(const QString& title, const QString& message, int timerSec); - void shareConfig(const QString& data, const QString& suggestedName); + void setNotificationText(const QString &title, const QString &message, int timerSec); + void shareConfig(const QString &data, const QString &suggestedName); void setFallbackConnectedNotification(); - void getBackendLogs(std::function&& callback); + void getBackendLogs(std::function &&callback); void cleanupBackendLogs(); - void importConfig(const QString& data); const QJsonObject &vpnConfig() const; void setVpnConfig(const QJsonObject &newVpnConfig); @@ -45,18 +41,20 @@ public: void startQrReaderActivity(); signals: - void connectionStateChanged(VpnProtocol::VpnConnectionState state); + void connectionStateChanged(Vpn::ConnectionState state); // This signal is emitted when the controller is initialized. Note that the // VPN tunnel can be already active. In this case, "connected" should be set // to true and the "connectionDate" should be set to the activation date if // known. // If "status" is set to false, the backend service is considered unavailable. - void initialized(bool status, bool connected, const QDateTime& connectionDate); + void initialized(bool status, bool connected, const QDateTime &connectionDate); void statusUpdated(QString totalRx, QString totalTx, QString endpoint, QString deviceIPv4); void scheduleStatusCheckSignal(); + void importConfigFromOutside(QString &data); + protected slots: void scheduleStatusCheckSlot(); @@ -65,12 +63,10 @@ private: QJsonObject m_vpnConfig; - StartPageLogic *m_startPageLogic; - bool m_serviceConnected = false; - std::function m_logCallback; + std::function m_logCallback; - static void startActivityForResult(JNIEnv* env, jobject /*thiz*/, jobject intent); + static void startActivityForResult(JNIEnv *env, jobject /*thiz*/, jobject intent); bool isConnected = false; diff --git a/client/platforms/android/authResultReceiver.cpp b/client/platforms/android/authResultReceiver.cpp new file mode 100644 index 00000000..21e838a2 --- /dev/null +++ b/client/platforms/android/authResultReceiver.cpp @@ -0,0 +1,16 @@ +#include "authResultReceiver.h" + +AuthResultReceiver::AuthResultReceiver(QSharedPointer ¬ifier) : m_notifier(notifier) +{ +} + +void AuthResultReceiver::handleActivityResult(int receiverRequestCode, int resultCode, const QJniObject &data) +{ + qDebug() << "receiverRequestCode" << receiverRequestCode << "resultCode" << resultCode; + + if (resultCode == -1) { // ResultOK + emit m_notifier->authSuccessful(); + } else { + emit m_notifier->authFailed(); + } +} diff --git a/client/platforms/android/authResultReceiver.h b/client/platforms/android/authResultReceiver.h new file mode 100644 index 00000000..9a88dcf5 --- /dev/null +++ b/client/platforms/android/authResultReceiver.h @@ -0,0 +1,32 @@ +#ifndef AUTHRESULTRECEIVER_H +#define AUTHRESULTRECEIVER_H + +#include + +#include + +class AuthResultNotifier : public QObject +{ + Q_OBJECT + +public: + AuthResultNotifier(QObject *parent = nullptr) : QObject(parent) {}; + +signals: + void authFailed(); + void authSuccessful(); +}; + +/* Auth result handler for Android */ +class AuthResultReceiver final : public QAndroidActivityResultReceiver +{ +public: + AuthResultReceiver(QSharedPointer ¬ifier); + + void handleActivityResult(int receiverRequestCode, int resultCode, const QJniObject &data) override; + +private: + QSharedPointer m_notifier; +}; + +#endif // AUTHRESULTRECEIVER_H diff --git a/client/protocols/wireguardprotocol.cpp b/client/protocols/wireguardprotocol.cpp index f48c7dfd..66fec366 100644 --- a/client/protocols/wireguardprotocol.cpp +++ b/client/protocols/wireguardprotocol.cpp @@ -5,27 +5,28 @@ #include #include "logger.h" -#include "wireguardprotocol.h" #include "utilities.h" +#include "wireguardprotocol.h" #include "mozilla/localsocketcontroller.h" -WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject* parent) : VpnProtocol(configuration, parent) +WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *parent) + : VpnProtocol(configuration, parent) { m_configFile.setFileName(QDir::tempPath() + QDir::separator() + serviceName() + ".conf"); writeWireguardConfiguration(configuration); // MZ #if defined(MZ_LINUX) - //m_impl.reset(new LinuxController()); + // m_impl.reset(new LinuxController()); #elif defined(MZ_MACOS) // || defined(MZ_WINDOWS) m_impl.reset(new LocalSocketController()); - connect(m_impl.get(), &ControllerImpl::connected, this, [this](const QString& pubkey, const QDateTime& connectionTimestamp) { - emit connectionStateChanged(VpnProtocol::Connected); - }); - connect(m_impl.get(), &ControllerImpl::disconnected, this, [this](){ - emit connectionStateChanged(VpnProtocol::Disconnected); - }); + connect(m_impl.get(), &ControllerImpl::connected, this, + [this](const QString &pubkey, const QDateTime &connectionTimestamp) { + emit connectionStateChanged(Vpn::ConnectionState::Connected); + }); + connect(m_impl.get(), &ControllerImpl::disconnected, this, + [this]() { emit connectionStateChanged(Vpn::ConnectionState::Disconnected); }); m_impl->initialize(nullptr, nullptr); #endif } @@ -74,9 +75,10 @@ void WireguardProtocol::stop() setConnectionState(Vpn::ConnectionState::Disconnected); }); - connect(m_wireguardStopProcess.data(), &PrivilegedProcess::stateChanged, this, [this](QProcess::ProcessState newState) { - qDebug() << "WireguardProtocol::WireguardProtocol Stop stateChanged" << newState; - }); + connect(m_wireguardStopProcess.data(), &PrivilegedProcess::stateChanged, this, + [this](QProcess::ProcessState newState) { + qDebug() << "WireguardProtocol::WireguardProtocol Stop stateChanged" << newState; + }); #ifdef Q_OS_LINUX if (IpcClient::Interface()) { @@ -143,8 +145,8 @@ void WireguardProtocol::writeWireguardConfiguration(const QJsonObject &configura m_isConfigLoaded = true; qDebug().noquote() << QString("Set config data") << configPath(); - qDebug().noquote() << QString("Set config data") << configuration.value(ProtocolProps::key_proto_config_data(Proto::WireGuard)).toString().toUtf8(); - + qDebug().noquote() << QString("Set config data") + << configuration.value(ProtocolProps::key_proto_config_data(Proto::WireGuard)).toString().toUtf8(); } QString WireguardProtocol::configPath() const @@ -156,7 +158,8 @@ void WireguardProtocol::updateRouteGateway(QString line) { // TODO: fix for macos line = line.split("ROUTE_GATEWAY", Qt::SkipEmptyParts).at(1); - if (!line.contains("/")) return; + if (!line.contains("/")) + return; m_routeGateway = line.split("/", Qt::SkipEmptyParts).first(); m_routeGateway.replace(" ", ""); qDebug() << "Set VPN route gateway" << m_routeGateway; @@ -216,13 +219,13 @@ ErrorCode WireguardProtocol::start() setConnectionState(Vpn::ConnectionState::Disconnected); }); - connect(m_wireguardStartProcess.data(), &PrivilegedProcess::stateChanged, this, [this](QProcess::ProcessState newState) { - qDebug() << "WireguardProtocol::WireguardProtocol stateChanged" << newState; - }); + connect(m_wireguardStartProcess.data(), &PrivilegedProcess::stateChanged, this, + [this](QProcess::ProcessState newState) { + qDebug() << "WireguardProtocol::WireguardProtocol stateChanged" << newState; + }); - connect(m_wireguardStartProcess.data(), &PrivilegedProcess::finished, this, [this]() { - setConnectionState(Vpn::ConnectionState::Connected); - }); + connect(m_wireguardStartProcess.data(), &PrivilegedProcess::finished, this, + [this]() { setConnectionState(Vpn::ConnectionState::Connected); }); connect(m_wireguardStartProcess.data(), &PrivilegedProcess::readyRead, this, [this]() { QRemoteObjectPendingReply reply = m_wireguardStartProcess->readAll(); @@ -250,7 +253,6 @@ ErrorCode WireguardProtocol::start() void WireguardProtocol::updateVpnGateway(const QString &line) { - } QString WireguardProtocol::serviceName() const @@ -261,9 +263,9 @@ QString WireguardProtocol::serviceName() const QStringList WireguardProtocol::stopArgs() { #ifdef Q_OS_WIN - return {"--remove", configPath()}; + return { "--remove", configPath() }; #elif defined Q_OS_LINUX - return {"down", "wg99"}; + return { "down", "wg99" }; #else return {}; #endif @@ -272,11 +274,10 @@ QStringList WireguardProtocol::stopArgs() QStringList WireguardProtocol::startArgs() { #ifdef Q_OS_WIN - return {"--add", configPath()}; + return { "--add", configPath() }; #elif defined Q_OS_LINUX - return {"up", "wg99"}; + return { "up", "wg99" }; #else return {}; #endif } - diff --git a/client/resources.qrc b/client/resources.qrc index e1afa294..85ee838f 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -194,7 +194,6 @@ ui/qml/Controls2/HorizontalRadioButton.qml ui/qml/Controls2/VerticalRadioButton.qml ui/qml/Controls2/SwitcherType.qml - ui/qml/Pages2/PageTest.qml ui/qml/Controls2/TabButtonType.qml ui/qml/Pages2/PageSetupWizardProtocolSettings.qml ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -278,5 +277,6 @@ ui/qml/Pages2/PageServiceSftpSettings.qml images/controls/copy.svg ui/qml/Pages2/PageServiceTorWebsiteSettings.qml + ui/qml/Pages2/PageSetupWizardQrReader.qml diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index c989422d..561222f2 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -11,9 +11,12 @@ #include "configurators/openvpn_configurator.h" #include "configurators/wireguard_configurator.h" -#include "qrcodegen.hpp" - #include "core/errorstrings.h" +#ifdef Q_OS_ANDROID + #include "platforms/android/android_controller.h" + #include "platforms/android/androidutils.h" +#endif +#include "qrcodegen.hpp" ExportController::ExportController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, @@ -25,6 +28,14 @@ ExportController::ExportController(const QSharedPointer &serversMo m_settings(settings), m_configurator(configurator) { +#ifdef Q_OS_ANDROID + m_authResultNotifier.reset(new AuthResultNotifier); + m_authResultReceiver.reset(new AuthResultReceiver(m_authResultNotifier)); + connect(m_authResultNotifier.get(), &AuthResultNotifier::authFailed, this, + [this]() { emit exportErrorOccurred(tr("Access error!")); }); + connect(m_authResultNotifier.get(), &AuthResultNotifier::authSuccessful, this, + &ExportController::generateFullAccessConfig); +#endif } void ExportController::generateFullAccessConfig() @@ -44,6 +55,27 @@ void ExportController::generateFullAccessConfig() emit exportConfigChanged(); } +#if defined(Q_OS_ANDROID) +void ExportController::generateFullAccessConfigAndroid() +{ + /* We use builtin keyguard for ssh key export protection on Android */ + QJniObject activity = AndroidUtils::getActivity(); + auto appContext = activity.callObjectMethod("getApplicationContext", "()Landroid/content/Context;"); + if (appContext.isValid()) { + auto intent = QJniObject::callStaticObjectMethod("org/amnezia/vpn/AuthHelper", "getAuthIntent", + "(Landroid/content/Context;)Landroid/content/Intent;", + appContext.object()); + if (intent.isValid()) { + if (intent.object() != nullptr) { + QtAndroidPrivate::startActivity(intent.object(), 1, m_authResultReceiver.get()); + } + } else { + generateFullAccessConfig(); + } + } +} +#endif + void ExportController::generateConnectionConfig() { clearPreviousConfig(); @@ -194,6 +226,35 @@ void ExportController::saveFile() QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); } +void ExportController::shareFile() +{ +#if defined Q_OS_IOS + ext.replace("*", ""); + QString fileName = QDir::tempPath() + "/" + suggestedName; + + if (fileName.isEmpty()) + return; + if (!fileName.endsWith(ext)) + fileName.append(ext); + + QFile::remove(fileName); + + QFile save(fileName); + save.open(QIODevice::WriteOnly); + save.write(data.toUtf8()); + save.close(); + + QStringList filesToSend; + filesToSend.append(fileName); + MobileUtils::shareText(filesToSend); + return; +#endif +#if defined Q_OS_ANDROID + AndroidController::instance()->shareConfig(m_config, "amnezia_config"); + return; +#endif +} + QList ExportController::generateQrCodeImageSeries(const QByteArray &data) { double k = 850; diff --git a/client/ui/controllers/exportController.h b/client/ui/controllers/exportController.h index e4a37a96..7af2cd85 100644 --- a/client/ui/controllers/exportController.h +++ b/client/ui/controllers/exportController.h @@ -6,6 +6,9 @@ #include "configurators/vpn_configurator.h" #include "ui/models/containers_model.h" #include "ui/models/servers_model.h" +#ifdef Q_OS_ANDROID + #include "platforms/android/authResultReceiver.h" +#endif class ExportController : public QObject { @@ -22,6 +25,9 @@ public: public slots: void generateFullAccessConfig(); +#if defined(Q_OS_ANDROID) + void generateFullAccessConfigAndroid(); +#endif void generateConnectionConfig(); void generateOpenVpnConfig(); void generateWireGuardConfig(); @@ -30,6 +36,7 @@ public slots: QList getQrCodes(); void saveFile(); + void shareFile(); signals: void generateConfig(int type); @@ -52,6 +59,11 @@ private: QString m_config; QList m_qrCodes; + +#ifdef Q_OS_ANDROID + QSharedPointer m_authResultNotifier; + QSharedPointer m_authResultReceiver; +#endif }; #endif // EXPORTCONTROLLER_H diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 5b4b7a83..218f43cb 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -2,8 +2,15 @@ #include #include +#include #include "core/errorstrings.h" +#ifdef Q_OS_ANDROID + #include "../../platforms/android/android_controller.h" + #include "../../platforms/android/androidutils.h" + #include +#endif +#include "utilities.h" namespace { @@ -41,33 +48,58 @@ ImportController::ImportController(const QSharedPointer &serversMo const std::shared_ptr &settings, QObject *parent) : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings) { +#ifdef Q_OS_ANDROID + // Set security screen for Android app + AndroidUtils::runOnAndroidThreadSync([]() { + QJniObject activity = AndroidUtils::getActivity(); + QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;"); + if (window.isValid()) { + const int FLAG_SECURE = 8192; + window.callMethod("addFlags", "(I)V", FLAG_SECURE); + } + }); +#endif } -void ImportController::extractConfigFromFile(const QUrl &fileUrl) +void ImportController::extractConfigFromFile() { - QFile file(fileUrl.toLocalFile()); + QString fileName = Utils::getFileName(Q_NULLPTR, tr("Open config file"), + QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), + "*.vpn *.ovpn *.conf"); + QFile file(fileName); if (file.open(QIODevice::ReadOnly)) { QString data = file.readAll(); - auto configFormat = checkConfigFormat(data); - if (configFormat == ConfigTypes::OpenVpn) { - m_config = extractOpenVpnConfig(data); - } else if (configFormat == ConfigTypes::WireGuard) { - m_config = extractWireGuardConfig(data); - } else { - m_config = extractAmneziaConfig(data); - } - + extractConfigFromData(data); m_configFileName = QFileInfo(file.fileName()).fileName(); } } +void ImportController::extractConfigFromData(QString &data) +{ + auto configFormat = checkConfigFormat(data); + if (configFormat == ConfigTypes::OpenVpn) { + m_config = extractOpenVpnConfig(data); + } else if (configFormat == ConfigTypes::WireGuard) { + m_config = extractWireGuardConfig(data); + } else { + m_config = extractAmneziaConfig(data); + } +} + void ImportController::extractConfigFromCode(QString code) { m_config = extractAmneziaConfig(code); m_configFileName = ""; } +void ImportController::extractConfigFromQr() +{ +#ifdef Q_OS_ANDROID + AndroidController::instance()->startQrReaderActivity(); +#endif +} + QString ImportController::getConfig() { return QJsonDocument(m_config).toJson(QJsonDocument::Indented); diff --git a/client/ui/controllers/importController.h b/client/ui/controllers/importController.h index 561ea19c..273b12a5 100644 --- a/client/ui/controllers/importController.h +++ b/client/ui/controllers/importController.h @@ -3,10 +3,10 @@ #include -#include "core/defs.h" #include "containers/containers_defs.h" -#include "ui/models/servers_model.h" +#include "core/defs.h" #include "ui/models/containers_model.h" +#include "ui/models/servers_model.h" class ImportController : public QObject { @@ -14,13 +14,14 @@ class ImportController : public QObject public: explicit ImportController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, - const std::shared_ptr &settings, - QObject *parent = nullptr); + const std::shared_ptr &settings, QObject *parent = nullptr); public slots: void importConfig(); - void extractConfigFromFile(const QUrl &fileUrl); + void extractConfigFromFile(); + void extractConfigFromData(QString &data); void extractConfigFromCode(QString code); + void extractConfigFromQr(); QString getConfig(); QString getConfigFileName(); @@ -39,7 +40,6 @@ private: QJsonObject m_config; QString m_configFileName; - }; #endif // IMPORTCONTROLLER_H diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 84633b54..68e47a89 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -8,6 +8,34 @@ #include "core/servercontroller.h" #include "utilities.h" +namespace +{ +#ifdef Q_OS_WINDOWS + QString getNextDriverLetter() + { + QProcess drivesProc; + drivesProc.start("wmic logicaldisk get caption"); + drivesProc.waitForFinished(); + QString drives = drivesProc.readAll(); + qDebug() << drives; + + QString letters = "CFGHIJKLMNOPQRSTUVWXYZ"; + QString letter; + for (int i = letters.size() - 1; i > 0; i--) { + letter = letters.at(i); + if (!drives.contains(letter + ":")) + break; + } + if (letter == "C:") { + // set err info + qDebug() << "Can't find free drive letter"; + return ""; + } + return letter; + } +#endif +} + InstallController::InstallController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, const std::shared_ptr &settings, QObject *parent) @@ -15,6 +43,17 @@ InstallController::InstallController(const QSharedPointer &servers { } +InstallController::~InstallController() +{ +#ifdef Q_OS_WINDOWS + for (QSharedPointer process : m_sftpMountProcesses) { + Utils::signalCtrl(process->processId(), CTRL_C_EVENT); + process->kill(); + process->waitForFinished(); + } +#endif +} + void InstallController::install(DockerContainer container, int port, TransportProto transportProto) { Proto mainProto = ContainerProps::defaultProtocol(container); diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index 8458b46d..fdd0ebed 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -16,6 +16,7 @@ public: explicit InstallController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, const std::shared_ptr &settings, QObject *parent = nullptr); + ~InstallController(); public slots: void install(DockerContainer container, int port, TransportProto transportProto); diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/pageController.cpp index 4ee9b3bf..5abeb77f 100644 --- a/client/ui/controllers/pageController.cpp +++ b/client/ui/controllers/pageController.cpp @@ -1,7 +1,9 @@ #include "pageController.h" -PageController::PageController(const QSharedPointer &serversModel, - QObject *parent) : QObject(parent), m_serversModel(serversModel) +#include + +PageController::PageController(const QSharedPointer &serversModel, QObject *parent) + : QObject(parent), m_serversModel(serversModel) { } @@ -24,3 +26,24 @@ QString PageController::getPagePath(PageLoader::PageEnum page) QString pageName = metaEnum.valueToKey(static_cast(page)); return "qrc:/ui/qml/Pages2/" + pageName + ".qml"; } + +void PageController::closeWindow() +{ +#ifdef Q_OS_ANDROID + qApp->quit(); +#else + if (m_serversModel->getServersCount() == 0) { + qApp->quit(); + } else { + emit hideMainWindow(); + } +#endif +} + +void PageController::keyPressEvent(Qt::Key key) +{ + switch (key) { + case Qt::Key_Back: emit closePage(); + default: return; + } +} diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index d79b100a..e8452b45 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -41,6 +41,7 @@ namespace PageLoader PageSetupWizardConfigSource, PageSetupWizardTextKey, PageSetupWizardViewConfig, + PageSetupWizardQrReader, PageProtocolOpenVpnSettings, PageProtocolShadowSocksSettings, @@ -67,15 +68,25 @@ public slots: QString getInitialPage(); QString getPagePath(PageLoader::PageEnum page); + void closeWindow(); + void keyPressEvent(Qt::Key key); + signals: void goToPageHome(); void goToPageSettings(); + void goToPageViewConfig(); + void closePage(); + void restorePageHomeState(bool isContainerInstalled = false); void replaceStartPage(); + void showErrorMessage(QString errorMessage); void showInfoMessage(QString message); + void showBusyIndicator(bool visible); - void raise(); + + void hideMainWindow(); + void raiseMainWindow(); private: QSharedPointer m_serversModel; diff --git a/client/ui/pages_logic/ServerSettingsLogic.cpp b/client/ui/pages_logic/ServerSettingsLogic.cpp index a17f0159..4c68b549 100644 --- a/client/ui/pages_logic/ServerSettingsLogic.cpp +++ b/client/ui/pages_logic/ServerSettingsLogic.cpp @@ -6,20 +6,21 @@ #include "VpnLogic.h" #include "core/errorstrings.h" -#include #include +#include #if defined(Q_OS_ANDROID) -#include "../../platforms/android/androidutils.h" + #include "../../platforms/android/androidutils.h" #endif -ServerSettingsLogic::ServerSettingsLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent), - m_labelWaitInfoVisible{true}, - m_pushButtonClearClientCacheVisible{true}, - m_pushButtonShareFullVisible{true}, - m_pushButtonClearClientCacheText{tr("Clear client cached profile")} -{ } +ServerSettingsLogic::ServerSettingsLogic(UiLogic *logic, QObject *parent) + : PageLogicBase(logic, parent), + m_labelWaitInfoVisible { true }, + m_pushButtonClearClientCacheVisible { true }, + m_pushButtonShareFullVisible { true }, + m_pushButtonClearClientCacheText { tr("Clear client cached profile") } +{ +} void ServerSettingsLogic::onUpdatePage() { @@ -33,11 +34,11 @@ void ServerSettingsLogic::onUpdatePage() const QString &userName = server.value(config_key::userName).toString(); const QString &hostName = server.value(config_key::hostName).toString(); QString name = QString("%1%2%3%4%5") - .arg(userName) - .arg(userName.isEmpty() ? "" : "@") - .arg(hostName) - .arg(port.isEmpty() ? "" : ":") - .arg(port); + .arg(userName) + .arg(userName.isEmpty() ? "" : "@") + .arg(hostName) + .arg(port.isEmpty() ? "" : ":") + .arg(port); set_labelServerText(name); set_lineEditDescriptionText(server.value(config_key::description).toString()); @@ -49,15 +50,15 @@ void ServerSettingsLogic::onUpdatePage() void ServerSettingsLogic::onPushButtonForgetServer() { - if (m_settings->defaultServerIndex() == uiLogic()->m_selectedServerIndex && uiLogic()->m_vpnConnection->isConnected()) { + if (m_settings->defaultServerIndex() == uiLogic()->m_selectedServerIndex + && uiLogic()->m_vpnConnection->isConnected()) { uiLogic()->pageLogic()->onDisconnect(); } m_settings->removeServer(uiLogic()->m_selectedServerIndex); if (m_settings->defaultServerIndex() == uiLogic()->m_selectedServerIndex) { m_settings->setDefaultServer(0); - } - else if (m_settings->defaultServerIndex() > uiLogic()->m_selectedServerIndex) { + } else if (m_settings->defaultServerIndex() > uiLogic()->m_selectedServerIndex) { m_settings->setDefaultServer(m_settings->defaultServerIndex() - 1); } @@ -65,14 +66,12 @@ void ServerSettingsLogic::onPushButtonForgetServer() m_settings->setDefaultServer(-1); } - uiLogic()->m_selectedServerIndex = -1; uiLogic()->onUpdateAllPages(); if (m_settings->serversCount() == 0) { uiLogic()->setStartPage(Page::Start); - } - else { + } else { uiLogic()->closePage(); } } @@ -86,9 +85,7 @@ void ServerSettingsLogic::onPushButtonClearClientCacheClicked() m_settings->clearLastConnectionConfig(uiLogic()->m_selectedServerIndex, container); } - QTimer::singleShot(3000, this, [this]() { - set_pushButtonClearClientCacheText(tr("Clear client cached profile")); - }); + QTimer::singleShot(3000, this, [this]() { set_pushButtonClearClientCacheText(tr("Clear client cached profile")); }); } void ServerSettingsLogic::onLineEditDescriptionEditingFinished() @@ -111,7 +108,7 @@ void authResultReceiver::handleActivityResult(int receiverRequestCode, int resul { qDebug() << "receiverRequestCode" << receiverRequestCode << "resultCode" << resultCode; - if (resultCode == -1) { //ResultOK + if (resultCode == -1) { // ResultOK uiLogic()->pageLogic()->updateSharingPage(m_serverIndex, DockerContainer::None); emit uiLogic()->goToShareProtocolPage(Proto::Any); } @@ -121,26 +118,27 @@ void authResultReceiver::handleActivityResult(int receiverRequestCode, int resul void ServerSettingsLogic::onPushButtonShareFullClicked() { #if defined(Q_OS_ANDROID) -/* We use builtin keyguard for ssh key export protection on Android */ + /* We use builtin keyguard for ssh key export protection on Android */ QJniObject activity = AndroidUtils::getActivity(); - auto appContext = activity.callObjectMethod( - "getApplicationContext", "()Landroid/content/Context;"); + auto appContext = activity.callObjectMethod("getApplicationContext", "()Landroid/content/Context;"); if (appContext.isValid()) { QAndroidActivityResultReceiver *receiver = new authResultReceiver(uiLogic(), uiLogic()->m_selectedServerIndex); - auto intent = QJniObject::callStaticObjectMethod( - "org/amnezia/vpn/AuthHelper", "getAuthIntent", - "(Landroid/content/Context;)Landroid/content/Intent;", appContext.object()); + auto intent = QJniObject::callStaticObjectMethod("org/amnezia/vpn/AuthHelper", "getAuthIntent", + "(Landroid/content/Context;)Landroid/content/Intent;", + appContext.object()); if (intent.isValid()) { if (intent.object() != nullptr) { - QtAndroidPrivate::startActivity(intent.object(), 1, receiver); - } - } else { - uiLogic()->pageLogic()->updateSharingPage(uiLogic()->m_selectedServerIndex, DockerContainer::None); + QtAndroidPrivate::startActivity(intent.object(), 1, receiver); + } + } else { + uiLogic()->pageLogic()->updateSharingPage(uiLogic()->m_selectedServerIndex, + DockerContainer::None); emit uiLogic()->goToShareProtocolPage(Proto::Any); } } #else - uiLogic()->pageLogic()->updateSharingPage(uiLogic()->m_selectedServerIndex, DockerContainer::None); + uiLogic()->pageLogic()->updateSharingPage(uiLogic()->m_selectedServerIndex, + DockerContainer::None); emit uiLogic()->goToShareProtocolPage(Proto::Any); #endif } diff --git a/client/ui/pages_logic/StartPageLogic.cpp b/client/ui/pages_logic/StartPageLogic.cpp index 12490810..454b5fa3 100644 --- a/client/ui/pages_logic/StartPageLogic.cpp +++ b/client/ui/pages_logic/StartPageLogic.cpp @@ -1,68 +1,69 @@ #include "StartPageLogic.h" #include "ViewConfigLogic.h" -#include "core/errorstrings.h" +#include "../uilogic.h" #include "configurators/ssh_configurator.h" #include "configurators/vpn_configurator.h" -#include "../uilogic.h" -#include "utilities.h" +#include "core/errorstrings.h" #include "core/servercontroller.h" +#include "utilities.h" +#include #include #include -#include #ifdef Q_OS_ANDROID -#include -#include "../../platforms/android/androidutils.h" -#include "../../platforms/android/android_controller.h" + #include "../../platforms/android/android_controller.h" + #include "../../platforms/android/androidutils.h" + #include #endif -namespace { -enum class ConfigTypes { - Amnezia, - OpenVpn, - WireGuard -}; - -ConfigTypes checkConfigFormat(const QString &config) +namespace { - const QString openVpnConfigPatternCli = "client"; - const QString openVpnConfigPatternProto1 = "proto tcp"; - const QString openVpnConfigPatternProto2 = "proto udp"; - const QString openVpnConfigPatternDriver1 = "dev tun"; - const QString openVpnConfigPatternDriver2 = "dev tap"; + enum class ConfigTypes { + Amnezia, + OpenVpn, + WireGuard + }; - const QString wireguardConfigPatternSectionInterface = "[Interface]"; - const QString wireguardConfigPatternSectionPeer = "[Peer]"; + ConfigTypes checkConfigFormat(const QString &config) + { + const QString openVpnConfigPatternCli = "client"; + const QString openVpnConfigPatternProto1 = "proto tcp"; + const QString openVpnConfigPatternProto2 = "proto udp"; + const QString openVpnConfigPatternDriver1 = "dev tun"; + const QString openVpnConfigPatternDriver2 = "dev tap"; - if (config.contains(openVpnConfigPatternCli) && - (config.contains(openVpnConfigPatternProto1) || config.contains(openVpnConfigPatternProto2)) && - (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) { - return ConfigTypes::OpenVpn; - } else if (config.contains(wireguardConfigPatternSectionInterface) && - config.contains(wireguardConfigPatternSectionPeer)) - return ConfigTypes::WireGuard; - return ConfigTypes::Amnezia; -} + const QString wireguardConfigPatternSectionInterface = "[Interface]"; + const QString wireguardConfigPatternSectionPeer = "[Peer]"; + + if (config.contains(openVpnConfigPatternCli) + && (config.contains(openVpnConfigPatternProto1) || config.contains(openVpnConfigPatternProto2)) + && (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) { + return ConfigTypes::OpenVpn; + } else if (config.contains(wireguardConfigPatternSectionInterface) + && config.contains(wireguardConfigPatternSectionPeer)) + return ConfigTypes::WireGuard; + return ConfigTypes::Amnezia; + } } -StartPageLogic::StartPageLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent), - m_pushButtonConnectEnabled{true}, - m_pushButtonConnectText{tr("Connect")}, - m_pushButtonConnectKeyChecked{false}, - m_labelWaitInfoVisible{true}, - m_pushButtonBackFromStartVisible{true}, - m_ipAddressPortRegex{Utils::ipAddressPortRegExp()} +StartPageLogic::StartPageLogic(UiLogic *logic, QObject *parent) + : PageLogicBase(logic, parent), + m_pushButtonConnectEnabled { true }, + m_pushButtonConnectText { tr("Connect") }, + m_pushButtonConnectKeyChecked { false }, + m_labelWaitInfoVisible { true }, + m_pushButtonBackFromStartVisible { true }, + m_ipAddressPortRegex { Utils::ipAddressPortRegExp() } { #ifdef Q_OS_ANDROID // Set security screen for Android app AndroidUtils::runOnAndroidThreadSync([]() { QJniObject activity = AndroidUtils::getActivity(); QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;"); - if (window.isValid()){ + if (window.isValid()) { const int FLAG_SECURE = 8192; window.callMethod("addFlags", "(I)V", FLAG_SECURE); } @@ -89,17 +90,13 @@ void StartPageLogic::onUpdatePage() void StartPageLogic::onPushButtonConnect() { - if (pushButtonConnectKeyChecked()){ - if (lineEditIpText().isEmpty() || - lineEditLoginText().isEmpty() || - textEditSshKeyText().isEmpty() ) { + if (pushButtonConnectKeyChecked()) { + if (lineEditIpText().isEmpty() || lineEditLoginText().isEmpty() || textEditSshKeyText().isEmpty()) { set_labelWaitInfoText(tr("Please fill in all fields")); return; } } else { - if (lineEditIpText().isEmpty() || - lineEditLoginText().isEmpty() || - lineEditPasswordText().isEmpty() ) { + if (lineEditIpText().isEmpty() || lineEditLoginText().isEmpty() || lineEditPasswordText().isEmpty()) { set_labelWaitInfoText(tr("Please fill in all fields")); return; } @@ -174,7 +171,8 @@ void StartPageLogic::onPushButtonConnect() set_pushButtonConnectText(tr("Connect")); uiLogic()->m_installCredentials = serverCredentials; - if (ok) emit uiLogic()->goToPage(Page::NewServer); + if (ok) + emit uiLogic()->goToPage(Page::NewServer); } void StartPageLogic::onPushButtonImport() @@ -185,8 +183,10 @@ void StartPageLogic::onPushButtonImport() void StartPageLogic::onPushButtonImportOpenFile() { QString fileName = UiLogic::getOpenFileName(Q_NULLPTR, tr("Open config file"), - QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.vpn *.ovpn *.conf"); - if (fileName.isEmpty()) return; + QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), + "*.vpn *.ovpn *.conf"); + if (fileName.isEmpty()) + return; QFile file(fileName); file.open(QIODevice::ReadOnly); @@ -226,8 +226,7 @@ bool StartPageLogic::importConnection(const QJsonObject &profile) // check config uiLogic()->pageLogic()->set_configJson(profile); emit uiLogic()->goToPage(Page::ViewConfig); - } - else { + } else { qDebug() << "Failed to import profile"; qDebug().noquote() << QJsonDocument(profile).toJson(); return false; @@ -298,7 +297,6 @@ bool StartPageLogic::importConnectionFromOpenVpnConfig(const QString &config) o[config_key::defaultContainer] = "amnezia-openvpn"; o[config_key::description] = m_settings->nextAvailableServerName(); - const static QRegularExpression dnsRegExp("dhcp-option DNS (\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)"); QRegularExpressionMatchIterator dnsMatch = dnsRegExp.globalMatch(config); if (dnsMatch.hasNext()) { @@ -345,7 +343,9 @@ bool StartPageLogic::importConnectionFromWireguardConfig(const QString &config) o[config_key::defaultContainer] = "amnezia-wireguard"; o[config_key::description] = m_settings->nextAvailableServerName(); - const static QRegularExpression dnsRegExp("DNS = (\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b).*(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)"); + const static QRegularExpression dnsRegExp( + "DNS = " + "(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b).*(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)"); QRegularExpressionMatch dnsMatch = dnsRegExp.match(config); if (dnsMatch.hasMatch()) { o[config_key::dns1] = dnsMatch.captured(1); diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 05df413c..692289a8 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -54,10 +54,10 @@ DrawerType { Layout.fillWidth: true Layout.topMargin: 16 - text: qsTr("Save connection code") + text: Qt.platform.os === "android" ? qsTr("Share") : qsTr("Save connection code") onClicked: { - ExportController.saveFile() + Qt.platform.os === "android" ? ExportController.shareFile() : ExportController.saveFile() } } diff --git a/client/ui/qml/Controls2/CheckBoxType.qml b/client/ui/qml/Controls2/CheckBoxType.qml index 6ce5ac4e..e4b1703f 100644 --- a/client/ui/qml/Controls2/CheckBoxType.qml +++ b/client/ui/qml/Controls2/CheckBoxType.qml @@ -26,7 +26,7 @@ CheckBox { hoverEnabled: true indicator: Rectangle { - id: checkBoxBackground + id: background anchors.verticalCenter: parent.verticalCenter @@ -57,7 +57,6 @@ CheckBox { radius: 4 Image { - id: indicator anchors.centerIn: parent source: root.pressed ? imageSource : root.checked ? imageSource : "" @@ -71,31 +70,38 @@ CheckBox { } } - contentItem: ColumnLayout { - anchors.left: parent.left - anchors.right: parent.right - anchors.leftMargin: 8 + checkBoxBackground.width + contentItem: Item { + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight - spacing: 4 + anchors.fill: parent + anchors.leftMargin: 8 + background.width - ListItemTitleType { - Layout.fillWidth: true -// Layout.topMargin: 16 -// Layout.bottomMargin: description.visible ? 0 : 16 + ColumnLayout { + id: content - text: root.text - } + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter - CaptionTextType { - id: description + spacing: 4 - Layout.fillWidth: true - Layout.bottomMargin: 16 + ListItemTitleType { + Layout.fillWidth: true - text: root.descriptionText - color: "#878b91" + text: root.text + } - visible: root.descriptionText !== "" + CaptionTextType { + id: description + + Layout.fillWidth: true + + text: root.descriptionText + color: "#878b91" + + visible: root.descriptionText !== "" + } } } diff --git a/client/ui/qml/Controls2/PageType.qml b/client/ui/qml/Controls2/PageType.qml index 046201b7..296fcc8c 100644 --- a/client/ui/qml/Controls2/PageType.qml +++ b/client/ui/qml/Controls2/PageType.qml @@ -20,7 +20,6 @@ Item { if (root.stackView.depth <= 1) { return } - root.stackView.pop() } diff --git a/client/ui/qml/Controls2/VerticalRadioButton.qml b/client/ui/qml/Controls2/VerticalRadioButton.qml index c833ca27..f44fa751 100644 --- a/client/ui/qml/Controls2/VerticalRadioButton.qml +++ b/client/ui/qml/Controls2/VerticalRadioButton.qml @@ -85,44 +85,50 @@ RadioButton { } } - contentItem: ColumnLayout { - id: content - anchors.left: parent.left - anchors.right: parent.right + contentItem: Item { + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight + + anchors.fill: parent anchors.leftMargin: 8 + background.width - spacing: 4 + ColumnLayout { + id: content - ListItemTitleType { - text: root.text + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter - color: { - if (root.checked) { - return selectedTextColor + spacing: 4 + + ListItemTitleType { + text: root.text + + color: { + if (root.checked) { + return selectedTextColor + } + return textColor + } + + Layout.fillWidth: true + + Behavior on color { + PropertyAnimation { duration: 200 } } - return textColor } - Layout.fillWidth: true - Layout.topMargin: 16 - Layout.bottomMargin: description.visible ? 0 : 16 + CaptionTextType { + id: description - Behavior on color { - PropertyAnimation { duration: 200 } + color: "#878B91" + text: root.descriptionText + + visible: root.descriptionText !== "" + + Layout.fillWidth: true } } - - CaptionTextType { - id: description - - color: "#878B91" - text: root.descriptionText - - visible: root.descriptionText !== "" - - Layout.fillWidth: true - Layout.bottomMargin: 16 - } } MouseArea { diff --git a/client/ui/qml/Pages2/PageSettingsAbout.qml b/client/ui/qml/Pages2/PageSettingsAbout.qml index 01b11bda..1875f085 100644 --- a/client/ui/qml/Pages2/PageSettingsAbout.qml +++ b/client/ui/qml/Pages2/PageSettingsAbout.qml @@ -43,8 +43,8 @@ PageType { Layout.topMargin: 16 Layout.leftMargin: 16 Layout.rightMargin: 16 - Layout.preferredWidth: 344 - Layout.preferredHeight: 279 + Layout.preferredWidth: 291 + Layout.preferredHeight: 224 } Header2TextType { @@ -100,7 +100,7 @@ And if you don't like the app, all the more support it - the donation will be us text: qsTr("Show other methods on Github") - onClicked: Qt.openUrlExternally("https://github.com/amnezia-vpn/amnezia-client") + onClicked: Qt.openUrlExternally("https://github.com/amnezia-vpn/amnezia-client#donate") } ParagraphTextType { diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 45e4c29c..78bd7486 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -61,16 +61,18 @@ PageType { leftImageSource: "qrc:/images/controls/folder-open.svg" clickedFunction: function() { - onClicked: fileDialog.open() +// onClicked: fileDialog.open() + ImportController.extractConfigFromFile() + goToPage(PageEnum.PageSetupWizardViewConfig) } - FileDialog { - id: fileDialog - onAccepted: { - ImportController.extractConfigFromFile(selectedFile) - goToPage(PageEnum.PageSetupWizardViewConfig) - } - } +// FileDialog { +// id: fileDialog +// onAccepted: { +// ImportController.extractConfigFromFile(selectedFile) +// goToPage(PageEnum.PageSetupWizardViewConfig) +// } +// } } DividerType {} @@ -84,6 +86,8 @@ PageType { leftImageSource: "qrc:/images/controls/qr-code.svg" clickedFunction: function() { + ImportController.extractConfigFromQr() +// goToPage(PageEnum.PageSetupWizardQrReader) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardQrReader.qml b/client/ui/qml/Pages2/PageSetupWizardQrReader.qml new file mode 100644 index 00000000..65d233ef --- /dev/null +++ b/client/ui/qml/Pages2/PageSetupWizardQrReader.qml @@ -0,0 +1,52 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs + +import PageEnum 1.0 +import QRCodeReader 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +PageType { + id: root + + ColumnLayout { + anchors.fill: parent + + spacing: 0 + + BackButtonType { + Layout.topMargin: 20 + } + + ParagraphTextType { + Layout.fillWidth: true + + text: qsTr("Point the camera at the QR code and hold for a couple of seconds.") + } + + ProgressBarType { + + } + + Item { + Layout.fillHeight: true + Layout.fillWidth: true + + QRCodeReader { + id: qrCodeReader + Component.onCompleted: { + qrCodeReader.setCameraSize(Qt.rect(parent.x, + parent.y, + parent.width, + parent.height)) + qrCodeReader.startReading() + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index 78ccfa95..750084c7 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -20,6 +20,10 @@ PageType { popupErrorMessage.popupErrorMessageText = errorMessage popupErrorMessage.open() } + + function onGoToPageViewConfig() { + goToPage(PageEnum.PageSetupWizardViewConfig) + } } FlickableType { diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 903e5658..201c630a 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -18,7 +18,7 @@ PageType { enum ConfigType { AmneziaConnection, - AmenziaFullAccess, + AmneziaFullAccess, OpenVpn, WireGuard } @@ -33,7 +33,14 @@ PageType { switch (type) { case PageShare.ConfigType.AmneziaConnection: ExportController.generateConnectionConfig(); break; - case PageShare.ConfigType.AmenziaFullAccess: ExportController.generateFullAccessConfig(); break; + case PageShare.ConfigType.AmneziaFullAccess: { + if (Qt.platform.os === "android") { + ExportController.generateFullAccessConfigAndroid(); + } else { + ExportController.generateFullAccessConfig(); + } + break; + } case PageShare.ConfigType.OpenVpn: ExportController.generateOpenVpnConfig(); break; case PageShare.ConfigType.WireGuard: ExportController.generateWireGuardConfig(); break; } @@ -48,6 +55,8 @@ PageType { } } + property string fullConfigServerSelectorText + property string connectionServerSelectorText property bool showContent: false property bool shareButtonEnabled: false property list connectionTypesModel: [ @@ -118,6 +127,7 @@ PageType { onClicked: { accessTypeSelector.currentIndex = 0 + serverSelector.text = root.connectionServerSelectorText } } @@ -129,6 +139,7 @@ PageType { onClicked: { accessTypeSelector.currentIndex = 1 + serverSelector.text = root.fullConfigServerSelectorText } } } @@ -176,14 +187,24 @@ PageType { currentIndex: 0 clickedFunction: function() { - serverSelector.text = selectedText - ServersModel.currentlyProcessedIndex = currentIndex - protocolSelector.visible = true - root.shareButtonEnabled = false + handler() + + if (accessTypeSelector.currentIndex === 0) { + protocolSelector.visible = true + root.shareButtonEnabled = false + } else { + serverSelector.menuVisible = false + } } Component.onCompleted: { + handler() + } + + function handler() { serverSelector.text = selectedText + root.fullConfigServerSelectorText = selectedText + root.connectionServerSelectorText = selectedText ServersModel.currentlyProcessedIndex = currentIndex } } @@ -260,7 +281,9 @@ PageType { } Component.onCompleted: { - handler() + if (accessTypeSelector.currentIndex === 0) { + handler() + } } function handler() { @@ -272,6 +295,8 @@ PageType { } serverSelector.text += ", " + selectedText + root.connectionServerSelectorText = serverSelector.text + shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(currentIndex)) diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 09bebaa1..5d49abf7 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -25,6 +25,11 @@ PageType { tabBarStackView.goToTabBarPage(PageController.getPagePath(PageEnum.PageSettings)) } + function onGoToPageViewConfig() { + var pagePath = PageController.getPagePath(PageEnum.PageSetupWizardViewConfig) + tabBarStackView.push(pagePath, { "objectName" : pagePath }, StackView.PushTransition) + } + function onShowErrorMessage(errorMessage) { popupErrorMessage.popupErrorMessageText = errorMessage popupErrorMessage.open() @@ -35,6 +40,13 @@ PageType { tabBarStackView.enabled = !visible tabBar.enabled = !visible } + + function onClosePage() { + if (tabBarStackView.depth <= 1) { + return + } + tabBarStackView.pop() + } } StackViewType { diff --git a/client/ui/qml/Pages2/PageTest.qml b/client/ui/qml/Pages2/PageTest.qml deleted file mode 100644 index d33608f8..00000000 --- a/client/ui/qml/Pages2/PageTest.qml +++ /dev/null @@ -1,282 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 - -import "./" -import "../Controls2" -import "../Config" -import "../Controls2/TextTypes" - -Item { - id: root - - ColumnLayout { - id: content - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - HeaderType { - id: header - - Layout.rightMargin: 16 - Layout.leftMargin: 16 - Layout.topMargin: 20 - Layout.bottomMargin: 32 - Layout.fillWidth: true - - backButtonImage: "qrc:/images/controls/arrow-left.svg" - headerText: "Server 1" - descriptionText: "root 192.168.111.111" - } - - Item { - Layout.fillWidth: true - - TabBar { - id: tabBar - - anchors { - top: parent.top - right: parent.right - left: parent.left - } - - background: Rectangle { - color: "transparent" - } - - TabButtonType { - id: bb - isSelected: tabBar.currentIndex === 0 - text: qsTr("Протоколы") - } - TabButtonType { - isSelected: tabBar.currentIndex === 1 - text: qsTr("Сервисы") - } - TabButtonType { - isSelected: tabBar.currentIndex === 2 - text: qsTr("Данные") - } - } - - StackLayout { - id: stackLayout - currentIndex: tabBar.currentIndex - - anchors.top: tabBar.bottom - anchors.topMargin: 16 - - width: parent.width - height: root.height - header.implicitHeight - tabBar.implicitHeight - 100 - - Item { - id: protocolsTab - - FlickableType { - anchors.top: parent.top - anchors.bottom: parent.bottom - contentHeight: protocolsTabContent.height - - ColumnLayout { - id: protocolsTabContent - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - BasicButtonType { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - text: qsTr("Forget this server") - } - - BasicButtonType { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - defaultColor: "transparent" - hoveredColor: Qt.rgba(255, 255, 255, 0.08) - pressedColor: Qt.rgba(255, 255, 255, 0.12) - disabledColor: "#878B91" - textColor: "#D7D8DB" - borderWidth: 1 - - text: qsTr("Forget this server") - } - - TextFieldWithHeaderType { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - headerText: "Server IP adress [:port]" - } - - LabelWithButtonType { - id: ip - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - text: "IP, логин и пароль от сервера" - rightImageSource: "qrc:/images/controls/chevron-right.svg" - } - - Rectangle { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - height: 1 - color: "#2C2D30" - } - - LabelWithButtonType { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - text: "QR-код, ключ или файл настроек" - rightImageSource: "qrc:/images/controls/chevron-right.svg" - } - - Rectangle { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - height: 1 - color: "#2C2D30" - } - - CardType { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - headerText: "Высокий" - bodyText: "Многие иностранные сайты и VPN-провайдеры заблокированы" - footerText: "футер" - } - - CardType { - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - headerText: "Высокий" - bodyText: "Многие иностранные сайты и VPN-провайдеры заблокированы" - footerText: "футер" - } - - DropDownType { - Layout.fillWidth: true - - text: "IP, логин и пароль от сервера" - descriptionText: "IP, логин и пароль от сервера" - - menuModel: [ - qsTr("SHA512"), - qsTr("SHA384"), - qsTr("SHA256"), - qsTr("SHA3-512"), - qsTr("SHA3-384"), - qsTr("SHA3-256"), - qsTr("whirlpool"), - qsTr("BLAKE2b512"), - qsTr("BLAKE2s256"), - qsTr("SHA1") - ] - } - } - } - } - - Item { - id: servicesTab - - FlickableType { - anchors.top: parent.top - anchors.bottom: parent.bottom - contentHeight: servicesTabContent.height - - ColumnLayout { - id: servicesTabContent - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - CheckBoxType { - Layout.leftMargin: 16 - Layout.rightMargin: 16 - Layout.fillWidth: true - text: qsTr("Auto-negotiate encryption") - } - CheckBoxType { - Layout.leftMargin: 16 - Layout.rightMargin: 16 - Layout.fillWidth: true - text: qsTr("Auto-negotiate encryption") - descriptionText: qsTr("Auto-negotiate encryption") - } - - Rectangle { - implicitWidth: buttonGroup.implicitWidth - implicitHeight: buttonGroup.implicitHeight - - Layout.leftMargin: 16 - Layout.rightMargin: 16 - - color: "#1C1D21" - radius: 16 - - RowLayout { - id: buttonGroup - - spacing: 0 - - HorizontalRadioButton { - implicitWidth: (root.width - 32) / 2 - text: "UDP" - } - - HorizontalRadioButton { - implicitWidth: (root.width - 32) / 2 - text: "TCP" - } - } - } - - VerticalRadioButton { - text: "Раздельное туннелирование" - descriptionText: "Позволяет подключаться к одним сайтам через защищенное соединение, а к другим в обход него" - checked: true - - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - } - - VerticalRadioButton { - text: "Раздельное туннелирование" - - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - } - - SwitcherType { - text: "Auto-negotiate encryption" - - Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - } - } - } - } - } - } - } -} diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index 8ad71d0f..d0f9880d 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -18,10 +18,9 @@ Window { color: "#0E0E11" - // todo onClosing: function() { console.debug("QML onClosing signal") - UiLogic.onCloseWindow() + PageController.closeWindow() } title: "AmneziaVPN" @@ -36,6 +35,11 @@ Window { var pagePath = PageController.getInitialPage() rootStackView.push(pagePath, { "objectName" : pagePath }) } + + Keys.onPressed: function(event) { + PageController.keyPressEvent(event.key) + event.accepted = true + } } Connections { @@ -49,10 +53,14 @@ Window { rootStackView.replace(pagePath, { "objectName" : pagePath }) } - function onRaise() { + function onRaiseMainWindow() { root.show() root.raise() root.requestActivate() } + + function onHideMainWindow() { + root.hide() + } } } diff --git a/client/ui/uilogic.cpp b/client/ui/uilogic.cpp index cd02bf41..aefcc49c 100644 --- a/client/ui/uilogic.cpp +++ b/client/ui/uilogic.cpp @@ -10,79 +10,76 @@ #include #include #include +#include +#include +#include #include #include #include -#include -#include -#include #include "amnezia_application.h" #include "configurators/cloak_configurator.h" -#include "configurators/vpn_configurator.h" #include "configurators/openvpn_configurator.h" #include "configurators/shadowsocks_configurator.h" #include "configurators/ssh_configurator.h" +#include "configurators/vpn_configurator.h" -#include "core/servercontroller.h" -#include "core/server_defs.h" #include "core/errorstrings.h" +#include "core/server_defs.h" +#include "core/servercontroller.h" #include "containers/containers_defs.h" #include "ui/qautostart.h" #include "logger.h" -#include "version.h" #include "uilogic.h" #include "utilities.h" +#include "version.h" #include "vpnconnection.h" #include #if defined Q_OS_MAC || defined Q_OS_LINUX -#include "ui/macos_util.h" + #include "ui/macos_util.h" #endif #ifdef Q_OS_ANDROID -#include "platforms/android/android_controller.h" + #include "platforms/android/android_controller.h" #endif #include "platforms/ios/MobileUtils.h" +#include "pages_logic/AdvancedServerSettingsLogic.h" #include "pages_logic/AppSettingsLogic.h" +#include "pages_logic/ClientInfoLogic.h" +#include "pages_logic/ClientManagementLogic.h" #include "pages_logic/GeneralSettingsLogic.h" #include "pages_logic/NetworkSettingsLogic.h" #include "pages_logic/NewServerProtocolsLogic.h" #include "pages_logic/QrDecoderLogic.h" #include "pages_logic/ServerConfiguringProgressLogic.h" +#include "pages_logic/ServerContainersLogic.h" #include "pages_logic/ServerListLogic.h" #include "pages_logic/ServerSettingsLogic.h" -#include "pages_logic/ServerContainersLogic.h" #include "pages_logic/ShareConnectionLogic.h" #include "pages_logic/SitesLogic.h" #include "pages_logic/StartPageLogic.h" #include "pages_logic/ViewConfigLogic.h" #include "pages_logic/VpnLogic.h" #include "pages_logic/WizardLogic.h" -#include "pages_logic/AdvancedServerSettingsLogic.h" -#include "pages_logic/ClientManagementLogic.h" -#include "pages_logic/ClientInfoLogic.h" #include "pages_logic/protocols/CloakLogic.h" #include "pages_logic/protocols/OpenVpnLogic.h" -#include "pages_logic/protocols/ShadowSocksLogic.h" #include "pages_logic/protocols/OtherProtocolsLogic.h" +#include "pages_logic/protocols/ShadowSocksLogic.h" #include "pages_logic/protocols/WireGuardLogic.h" using namespace amnezia; using namespace PageEnumNS; -UiLogic::UiLogic(std::shared_ptr settings, std::shared_ptr configurator, - QObject *parent) : - QObject(parent), - m_settings(settings), - m_configurator(configurator) +UiLogic::UiLogic(std::shared_ptr settings, std::shared_ptr configurator, QObject *parent) + : QObject(parent), m_settings(settings), m_configurator(configurator) { m_protocolsModel = new ProtocolsModel(settings, this); m_clientManagementModel = new ClientManagementModel(this); @@ -90,7 +87,6 @@ UiLogic::UiLogic(std::shared_ptr settings, std::shared_ptrmoveToThread(&m_vpnConnectionThread); m_vpnConnectionThread.start(); - m_protocolLogicMap.insert(Proto::OpenVpn, new OpenVpnLogic(this)); m_protocolLogicMap.insert(Proto::ShadowSocks, new ShadowSocksLogic(this)); m_protocolLogicMap.insert(Proto::Cloak, new CloakLogic(this)); @@ -99,7 +95,6 @@ UiLogic::UiLogic(std::shared_ptr settings, std::shared_ptr()->onConnectionStateChanged(Vpn::ConnectionState::Connected); - if (m_vpnConnection) m_vpnConnection->restoreConnection(); - } - }); - if (!AndroidController::instance()->initialize(pageLogic())) { - qCritical() << QString("Init failed"); - if (m_vpnConnection) m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Error); - return; - } + connect(AndroidController::instance(), &AndroidController::initialized, + [this](bool status, bool connected, const QDateTime &connectionDate) { + if (connected) { + pageLogic()->onConnectionStateChanged(Vpn::ConnectionState::Connected); + if (m_vpnConnection) + m_vpnConnection->restoreConnection(); + } + }); +// if (!AndroidController::instance()->initialize(pageLogic())) { +// qCritical() << QString("Init failed"); +// if (m_vpnConnection) m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Error); +// return; +// } #endif m_notificationHandler = NotificationHandler::create(qmlRoot()); - connect(m_vpnConnection, &VpnConnection::connectionStateChanged, m_notificationHandler, &NotificationHandler::setConnectionState); + connect(m_vpnConnection, &VpnConnection::connectionStateChanged, m_notificationHandler, + &NotificationHandler::setConnectionState); connect(m_notificationHandler, &NotificationHandler::raiseRequested, this, &UiLogic::raise); connect(m_notificationHandler, &NotificationHandler::connectRequested, pageLogic(), &VpnLogic::onConnect); - connect(m_notificationHandler, &NotificationHandler::disconnectRequested, pageLogic(), &VpnLogic::onDisconnect); + connect(m_notificationHandler, &NotificationHandler::disconnectRequested, pageLogic(), + &VpnLogic::onDisconnect); -// if (m_settings->serversCount() > 0) { -// if (m_settings->defaultServerIndex() < 0) m_settings->setDefaultServer(0); -// emit goToPage(Page::PageStart, true, false); -// } -// else { -// emit goToPage(Page::PageSetupWizardStart, true, false); -// } + // if (m_settings->serversCount() > 0) { + // if (m_settings->defaultServerIndex() < 0) m_settings->setDefaultServer(0); + // emit goToPage(Page::PageStart, true, false); + // } + // else { + // emit goToPage(Page::PageSetupWizardStart, true, false); + // } m_selectedServerIndex = m_settings->defaultServerIndex(); @@ -165,14 +164,13 @@ void UiLogic::initializeUiLogic() void UiLogic::showOnStartup() { - if (! m_settings->isStartMinimized()) { + if (!m_settings->isStartMinimized()) { emit show(); - } - else { + } else { #ifdef Q_OS_WIN emit hide(); #elif defined Q_OS_MACX - // TODO: fix: setDockIconVisible(false); + // TODO: fix: setDockIconVisible(false); #endif } } @@ -180,7 +178,7 @@ void UiLogic::showOnStartup() void UiLogic::onUpdateAllPages() { for (auto logic : m_logicMap) { - if (dynamic_cast(logic) || dynamic_cast(logic)) { + if (dynamic_cast(logic) || dynamic_cast(logic)) { continue; } logic->onUpdatePage(); @@ -191,57 +189,50 @@ void UiLogic::keyPressEvent(Qt::Key key) { switch (key) { case Qt::Key_AsciiTilde: - case Qt::Key_QuoteLeft: emit toggleLogPanel(); - break; - case Qt::Key_L: Logger::openLogsFolder(); - break; - case Qt::Key_K: Logger::openServiceLogsFolder(); - break; + case Qt::Key_QuoteLeft: emit toggleLogPanel(); break; + case Qt::Key_L: Logger::openLogsFolder(); break; + case Qt::Key_K: Logger::openServiceLogsFolder(); break; #ifdef QT_DEBUG - case Qt::Key_Q: - qApp->quit(); - break; + case Qt::Key_Q: qApp->quit(); break; case Qt::Key_H: m_selectedServerIndex = m_settings->defaultServerIndex(); m_selectedDockerContainer = m_settings->defaultContainer(m_selectedServerIndex); - //updateSharingPage(selectedServerIndex, m_settings->serverCredentials(selectedServerIndex), selectedDockerContainer); + // updateSharingPage(selectedServerIndex, m_settings->serverCredentials(selectedServerIndex), selectedDockerContainer); emit goToPage(Page::ShareConnection); break; #endif case Qt::Key_C: - qDebug().noquote() << "Def server" << m_settings->defaultServerIndex() << m_settings->defaultContainerName(m_settings->defaultServerIndex()); + qDebug().noquote() << "Def server" << m_settings->defaultServerIndex() + << m_settings->defaultContainerName(m_settings->defaultServerIndex()); qDebug().noquote() << QJsonDocument(m_settings->defaultServer()).toJson(); break; - case Qt::Key_A: - emit goToPage(Page::Start); - break; + case Qt::Key_A: emit goToPage(Page::Start); break; case Qt::Key_S: m_selectedServerIndex = m_settings->defaultServerIndex(); emit goToPage(Page::ServerSettings); break; - case Qt::Key_P: - onGotoCurrentProtocolsPage(); - break; + case Qt::Key_P: onGotoCurrentProtocolsPage(); break; case Qt::Key_T: m_configurator->sshConfigurator->openSshTerminal(m_settings->serverCredentials(m_settings->defaultServerIndex())); break; case Qt::Key_Escape: - if (currentPage() == Page::Vpn) break; - if (currentPage() == Page::ServerConfiguringProgress) break; + if (currentPage() == Page::Vpn) + break; + if (currentPage() == Page::ServerConfiguringProgress) + break; case Qt::Key_Back: -// if (currentPage() == Page::Start && pagesStack.size() < 2) break; -// if (currentPage() == Page::Sites && -// ui->tableView_sites->selectionModel()->selection().indexes().size() > 0) { -// ui->tableView_sites->clearSelection(); -// break; -// } + // if (currentPage() == Page::Start && pagesStack.size() < 2) break; + // if (currentPage() == Page::Sites && + // ui->tableView_sites->selectionModel()->selection().indexes().size() > 0) { + // ui->tableView_sites->clearSelection(); + // break; + // } - emit closePage(); + emit closePage(); //} - default: - ; + default:; } } @@ -250,8 +241,7 @@ void UiLogic::onCloseWindow() #ifdef Q_OS_ANDROID qApp->quit(); #else - if (m_settings->serversCount() == 0) - { + if (m_settings->serversCount() == 0) { qApp->quit(); } else { emit hide(); @@ -267,7 +257,6 @@ QString UiLogic::containerName(int container) QString UiLogic::containerDesc(int container) { return ContainerProps::containerDescriptions().value(static_cast(container)); - } void UiLogic::onGotoCurrentProtocolsPage() @@ -286,50 +275,50 @@ void UiLogic::installServer(QPair &container) qApp->processEvents(); ServerConfiguringProgressLogic::PageFunc pageFunc; - pageFunc.setEnabledFunc = [this] (bool enabled) -> void { + pageFunc.setEnabledFunc = [this](bool enabled) -> void { pageLogic()->set_pageEnabled(enabled); }; ServerConfiguringProgressLogic::ButtonFunc noButton; ServerConfiguringProgressLogic::LabelFunc waitInfoFunc; - waitInfoFunc.setTextFunc = [this] (const QString& text) -> void { + waitInfoFunc.setTextFunc = [this](const QString &text) -> void { pageLogic()->set_labelWaitInfoText(text); }; - waitInfoFunc.setVisibleFunc = [this] (bool visible) -> void { + waitInfoFunc.setVisibleFunc = [this](bool visible) -> void { pageLogic()->set_labelWaitInfoVisible(visible); }; ServerConfiguringProgressLogic::ProgressFunc progressBarFunc; - progressBarFunc.setVisibleFunc = [this] (bool visible) -> void { + progressBarFunc.setVisibleFunc = [this](bool visible) -> void { pageLogic()->set_progressBarVisible(visible); }; - progressBarFunc.setValueFunc = [this] (int value) -> void { + progressBarFunc.setValueFunc = [this](int value) -> void { pageLogic()->set_progressBarValue(value); }; - progressBarFunc.getValueFunc = [this] (void) -> int { + progressBarFunc.getValueFunc = [this](void) -> int { return pageLogic()->progressBarValue(); }; - progressBarFunc.getMaximumFunc = [this] (void) -> int { + progressBarFunc.getMaximumFunc = [this](void) -> int { return pageLogic()->progressBarMaximum(); }; - progressBarFunc.setTextVisibleFunc = [this] (bool visible) -> void { + progressBarFunc.setTextVisibleFunc = [this](bool visible) -> void { pageLogic()->set_progressBarTextVisible(visible); }; - progressBarFunc.setTextFunc = [this] (const QString& text) -> void { + progressBarFunc.setTextFunc = [this](const QString &text) -> void { pageLogic()->set_progressBarText(text); }; ServerConfiguringProgressLogic::LabelFunc busyInfoFunc; - busyInfoFunc.setTextFunc = [this] (const QString& text) -> void { + busyInfoFunc.setTextFunc = [this](const QString &text) -> void { pageLogic()->set_labelServerBusyText(text); }; - busyInfoFunc.setVisibleFunc = [this] (bool visible) -> void { + busyInfoFunc.setVisibleFunc = [this](bool visible) -> void { pageLogic()->set_labelServerBusyVisible(visible); }; ServerConfiguringProgressLogic::ButtonFunc cancelButtonFunc; - cancelButtonFunc.setVisibleFunc = [this] (bool visible) -> void { + cancelButtonFunc.setVisibleFunc = [this](bool visible) -> void { pageLogic()->set_pushButtonCancelVisible(visible); }; @@ -338,13 +327,12 @@ void UiLogic::installServer(QPair &container) if (errorCode == ErrorCode::NoError) { if (!isContainerAlreadyAddedToGui(container.first)) { progressBarFunc.setTextFunc(QString("Installing %1").arg(ContainerProps::containerToString(container.first))); - auto installAction = [&] () { + auto installAction = [&]() { ServerController serverController(m_settings); return serverController.setupContainer(m_installCredentials, container.first, container.second); }; - errorCode = pageLogic()->doInstallAction(installAction, pageFunc, progressBarFunc, - noButton, waitInfoFunc, - busyInfoFunc, cancelButtonFunc); + errorCode = pageLogic()->doInstallAction( + installAction, pageFunc, progressBarFunc, noButton, waitInfoFunc, busyInfoFunc, cancelButtonFunc); if (errorCode == ErrorCode::NoError) { if (!isServerCreated) { QJsonObject server; @@ -354,7 +342,7 @@ void UiLogic::installServer(QPair &container) server.insert(config_key::port, m_installCredentials.port); server.insert(config_key::description, m_settings->nextAvailableServerName()); - server.insert(config_key::containers, QJsonArray{container.second}); + server.insert(config_key::containers, QJsonArray { container.second }); server.insert(config_key::defaultContainer, ContainerProps::containerToString(container.first)); m_settings->addServer(server); @@ -371,22 +359,23 @@ void UiLogic::installServer(QPair &container) } } else { onUpdateAllPages(); - emit showWarningMessage("Attention! The container you are trying to install is already installed on the server. " - "All installed containers have been added to the application "); + emit showWarningMessage( + "Attention! The container you are trying to install is already installed on the server. " + "All installed containers have been added to the application "); emit setStartPage(Page::Vpn); return; } } - emit showWarningMessage(tr("Error occurred while configuring server.") + "\n" + - tr("Error message: ") + errorString(errorCode) + "\n" + - tr("See logs for details.")); + emit showWarningMessage(tr("Error occurred while configuring server.") + "\n" + tr("Error message: ") + + errorString(errorCode) + "\n" + tr("See logs for details.")); emit closePage(); } PageProtocolLogicBase *UiLogic::protocolLogic(Proto p) { PageProtocolLogicBase *logic = m_protocolLogicMap.value(p); - if (logic) return logic; + if (logic) + return logic; else { qCritical() << "UiLogic::protocolLogic Warning: logic missing for" << p; return new PageProtocolLogicBase(this); @@ -418,7 +407,7 @@ PageEnumNS::Page UiLogic::currentPage() return static_cast(currentPageValue()); } -void UiLogic::saveTextFile(const QString& desc, const QString& suggestedName, QString ext, const QString& data) +void UiLogic::saveTextFile(const QString &desc, const QString &suggestedName, QString ext, const QString &data) { #ifdef Q_OS_IOS shareTempFile(suggestedName, ext, data); @@ -429,17 +418,19 @@ void UiLogic::saveTextFile(const QString& desc, const QString& suggestedName, QS QString docDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); QUrl fileName; #ifdef AMNEZIA_DESKTOP - fileName = QFileDialog::getSaveFileUrl(nullptr, desc, - QUrl::fromLocalFile(docDir + "/" + suggestedName), "*" + ext); - if (fileName.isEmpty()) return; - if (!fileName.toString().endsWith(ext)) fileName = QUrl(fileName.toString() + ext); + fileName = QFileDialog::getSaveFileUrl(nullptr, desc, QUrl::fromLocalFile(docDir + "/" + suggestedName), "*" + ext); + if (fileName.isEmpty()) + return; + if (!fileName.toString().endsWith(ext)) + fileName = QUrl(fileName.toString() + ext); #elif defined Q_OS_ANDROID qDebug() << "UiLogic::shareConfig" << data; AndroidController::instance()->shareConfig(data, suggestedName); return; #endif - if (fileName.isEmpty()) return; + if (fileName.isEmpty()) + return; #ifdef AMNEZIA_DESKTOP QFile save(fileName.toLocalFile()); @@ -458,11 +449,13 @@ void UiLogic::saveTextFile(const QString& desc, const QString& suggestedName, QS void UiLogic::saveBinaryFile(const QString &desc, QString ext, const QString &data) { ext.replace("*", ""); - QString fileName = QFileDialog::getSaveFileName(nullptr, desc, - QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*" + ext); + QString fileName = QFileDialog::getSaveFileName( + nullptr, desc, QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*" + ext); - if (fileName.isEmpty()) return; - if (!fileName.endsWith(ext)) fileName.append(ext); + if (fileName.isEmpty()) + return; + if (!fileName.endsWith(ext)) + fileName.append(ext); QFile save(fileName); save.open(QIODevice::WriteOnly); @@ -478,12 +471,15 @@ void UiLogic::copyToClipboard(const QString &text) qApp->clipboard()->setText(text); } -void UiLogic::shareTempFile(const QString &suggestedName, QString ext, const QString& data) { +void UiLogic::shareTempFile(const QString &suggestedName, QString ext, const QString &data) +{ ext.replace("*", ""); QString fileName = QDir::tempPath() + "/" + suggestedName; - if (fileName.isEmpty()) return; - if (!fileName.endsWith(ext)) fileName.append(ext); + if (fileName.isEmpty()) + return; + if (!fileName.endsWith(ext)) + fileName.append(ext); QFile::remove(fileName); @@ -497,14 +493,14 @@ void UiLogic::shareTempFile(const QString &suggestedName, QString ext, const QSt MobileUtils::shareText(filesToSend); } -QString UiLogic::getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, - const QString &filter, QString *selectedFilter, QFileDialog::Options options) +QString UiLogic::getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, + QString *selectedFilter, QFileDialog::Options options) { QString fileName = QFileDialog::getOpenFileName(parent, caption, dir, filter, selectedFilter, options); #ifdef Q_OS_ANDROID // patch for files containing spaces etc - const QString sep {"raw%3A%2F"}; + const QString sep { "raw%3A%2F" }; if (fileName.startsWith("content://") && fileName.contains(sep)) { QString contentUrl = fileName.split(sep).at(0); QString rawUrl = fileName.split(sep).at(1); @@ -590,7 +586,8 @@ ErrorCode UiLogic::addAlreadyInstalledContainersGui(bool &isServerCreated) } if (createNewServer) { - server.insert(config_key::defaultContainer, ContainerProps::containerToString(installedContainers.firstKey())); + server.insert(config_key::defaultContainer, + ContainerProps::containerToString(installedContainers.firstKey())); m_settings->addServer(server); m_settings->setDefaultServer(m_settings->serversCount() - 1); isServerCreated = true; From b9a13d3a3243c4d00f69206a3e7958362c9ca3f0 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 24 Jul 2023 16:33:58 +0900 Subject: [PATCH 047/278] changed all text to english --- .../ConnectionTypeSelectionDrawer.qml | 6 ++--- client/ui/qml/Pages2/PageDeinstalling.qml | 2 +- client/ui/qml/Pages2/PageHome.qml | 2 +- .../ui/qml/Pages2/PageSettingsServerData.qml | 6 ++--- .../ui/qml/Pages2/PageSettingsServersList.qml | 2 +- .../Pages2/PageSetupWizardConfigSource.qml | 23 ++++++------------- .../qml/Pages2/PageSetupWizardInstalling.qml | 4 ++-- .../PageSetupWizardProtocolSettings.qml | 6 ++--- .../qml/Pages2/PageSetupWizardProtocols.qml | 4 ++-- client/ui/qml/Pages2/PageSetupWizardStart.qml | 7 +++--- 10 files changed, 26 insertions(+), 36 deletions(-) diff --git a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml index 71299b1f..8b377600 100644 --- a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml +++ b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml @@ -28,7 +28,7 @@ DrawerType { Layout.bottomMargin: 32 Layout.alignment: Qt.AlignHCenter - text: "Данные для подключения" + text: qsTr("Connection data") wrapMode: Text.WordWrap } @@ -37,7 +37,7 @@ DrawerType { Layout.fillWidth: true Layout.topMargin: 16 - text: "IP, логин и пароль от сервера" + text: qsTr("Server IP, login and password") rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { @@ -51,7 +51,7 @@ DrawerType { LabelWithButtonType { Layout.fillWidth: true - text: "QR-код, ключ или файл настроек" + text: qsTr("QR code, key or configuration file") rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { diff --git a/client/ui/qml/Pages2/PageDeinstalling.qml b/client/ui/qml/Pages2/PageDeinstalling.qml index 1dc542e0..49bcdb9a 100644 --- a/client/ui/qml/Pages2/PageDeinstalling.qml +++ b/client/ui/qml/Pages2/PageDeinstalling.qml @@ -81,7 +81,7 @@ PageType { Layout.fillWidth: true Layout.topMargin: 8 - text: "Обычно это занимает не больше 5 минут" + text: qsTr("Usually it takes no more than 5 minutes") } } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index b7d44c78..76b9f94b 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -163,7 +163,7 @@ PageType { text: root.currentContainerName textColor: "#0E0E11" - headerText: qsTr("Протокол подключения") + headerText: qsTr("Connection protocol") headerBackButtonImage: "qrc:/images/controls/arrow-left.svg" rootButtonClickedFunction: function() { diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index c5af47e0..20afecd2 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -22,7 +22,7 @@ PageType { if (isInstalledContainerFound) { message = qsTr("All installed containers have been added to the application") } else { - message = qsTr("Не найдено установленных контейнеров") + message = qsTr("No installed containers found") } PageController.showErrorMessage(message) @@ -84,8 +84,8 @@ PageType { visible: content.isServerWithWriteAccess Layout.fillWidth: true - text: qsTr("Проверить сервер на наличие ранее установленных сервисов Amnezia") - descriptionText: qsTr("Добавим их в приложение, если они не отображались") + text: qsTr("Check the server for previously installed Amnesia services") + descriptionText: qsTr("Add them to the application if they were not displayed") clickedFunction: function() { PageController.showBusyIndicator(true) diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index 4c6a9839..496ed370 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -36,7 +36,7 @@ PageType { actionButtonImage: "qrc:/images/controls/plus.svg" - headerText: "Серверы" + headerText: qsTr("Servers") actionButtonFunction: function() { connectionTypeSelection.visible = true diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 78bd7486..ae24c942 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -38,9 +38,9 @@ PageType { Layout.rightMargin: 16 Layout.leftMargin: 16 - headerText: "Подключение к серверу" - descriptionText: "Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватывать ваши данные.\n -Всё в порядке, если код передал друг." + headerText: qsTr("Server connection") + descriptionText: qsTr("Do not use connection code from public sources. It may have been created to intercept your data.\n +It's okay if a friend passed the code.") } Header2TextType { @@ -49,30 +49,21 @@ PageType { Layout.rightMargin: 16 Layout.leftMargin: 16 - text: "Что у вас есть?" + text: qsTr("What do you have?") } LabelWithButtonType { Layout.fillWidth: true Layout.topMargin: 16 - text: "Файл с настройками подключения" + text: qsTr("File with connection settings") rightImageSource: "qrc:/images/controls/chevron-right.svg" leftImageSource: "qrc:/images/controls/folder-open.svg" clickedFunction: function() { -// onClicked: fileDialog.open() ImportController.extractConfigFromFile() goToPage(PageEnum.PageSetupWizardViewConfig) } - -// FileDialog { -// id: fileDialog -// onAccepted: { -// ImportController.extractConfigFromFile(selectedFile) -// goToPage(PageEnum.PageSetupWizardViewConfig) -// } -// } } DividerType {} @@ -81,7 +72,7 @@ PageType { LabelWithButtonType { Layout.fillWidth: true - text: "QR-код" + text: qsTr("QR-code") rightImageSource: "qrc:/images/controls/chevron-right.svg" leftImageSource: "qrc:/images/controls/qr-code.svg" @@ -96,7 +87,7 @@ PageType { LabelWithButtonType { Layout.fillWidth: true - text: "Ключ в виде текста" + text: qsTr("Key as text") rightImageSource: "qrc:/images/controls/chevron-right.svg" leftImageSource: "qrc:/images/controls/text-cursor.svg" diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index 2f07db18..a0fdf7be 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -116,7 +116,7 @@ PageType { Layout.fillWidth: true Layout.topMargin: 20 - headerText: "Установка" + headerText: qsTr("Installing") descriptionText: name } @@ -142,7 +142,7 @@ PageType { Layout.fillWidth: true Layout.topMargin: 8 - text: "Обычно это занимает не больше 5 минут" + text: qsTr("Usually it takes no more than 5 minutes") } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index e593393c..29067cb2 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -193,7 +193,7 @@ PageType { Layout.topMargin: 16 - text: "Network protocol" + text: qsTr("Network protocol") } TransportProtoSelector { @@ -209,7 +209,7 @@ PageType { Layout.fillWidth: true Layout.topMargin: 16 - headerText: "Port" + headerText: qsTr("Port") } Rectangle { @@ -223,7 +223,7 @@ PageType { Layout.fillWidth: true Layout.bottomMargin: 32 - text: qsTr("Установить") + text: qsTr("Install") onClicked: function() { goToPage(PageEnum.PageSetupWizardInstalling); diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index 6a8e301f..f3de85b6 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -70,8 +70,8 @@ PageType { width: parent.width - headerText: "Протокол подключения" - descriptionText: "Выберите более приоритетный для вас. Позже можно будет установить остальные протоколы и доп сервисы, вроде DNS-прокси и SFTP." + headerText: qsTr("Connection protocol") + descriptionText: qsTr("Choose the one with the highest priority for you. Later, you can install other protocols and additional services, such as DNS proxy and SFTP.") } } diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index 750084c7..73438e34 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -58,7 +58,7 @@ PageType { Layout.leftMargin: 16 Layout.rightMargin: 16 - text: "Бесплатный сервис для создания личного VPN на вашем сервере. Помогаем получать доступ к заблокированному контенту, не раскрывая конфиденциальность даже провайдерам VPN." + text: qsTr("A free service to create a personal VPN on your server. We help you access blocked content without exposing your privacy even to VPN providers.") } BasicButtonType { @@ -67,7 +67,7 @@ PageType { Layout.leftMargin: 16 Layout.rightMargin: 16 - text: qsTr("У меня есть данные для подключения") + text: qsTr("I have the data to connect") onClicked: { connectionTypeSelection.visible = true @@ -87,10 +87,9 @@ PageType { textColor: "#D7D8DB" borderWidth: 1 - text: qsTr("У меня ничего нет") + text: qsTr("I have nothing") onClicked: { - goToPage(PageEnum.PageTest) } } } From 0411792ca5c2e614b151c497f7ab5685aa7467c8 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 25 Jul 2023 16:56:10 +0900 Subject: [PATCH 048/278] added qr-code decoder for android - added color change for status and navigation bar for android --- client/ui/controllers/exportController.cpp | 47 +++--- client/ui/controllers/exportController.h | 1 - client/ui/controllers/importController.cpp | 138 +++++++++++++++--- client/ui/controllers/importController.h | 24 ++- client/ui/controllers/pageController.cpp | 41 ++++++ client/ui/controllers/pageController.h | 3 + .../qml/Components/ShareConnectionDrawer.qml | 4 +- client/ui/qml/Controls2/DrawerType.qml | 14 ++ .../Pages2/PageSetupWizardConfigSource.qml | 10 +- .../qml/Pages2/PageSetupWizardInstalling.qml | 3 +- .../qml/Pages2/PageSetupWizardViewConfig.qml | 3 +- client/ui/qml/Pages2/PageStart.qml | 24 ++- client/ui/qml/main2.qml | 1 + 13 files changed, 255 insertions(+), 58 deletions(-) diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index 561222f2..6e4abb1f 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -202,31 +202,6 @@ QList ExportController::getQrCodes() } 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_config.toUtf8()); - save.close(); - - QFileInfo fi(fileName.toLocalFile()); - QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); -} - -void ExportController::shareFile() { #if defined Q_OS_IOS ext.replace("*", ""); @@ -253,6 +228,28 @@ void ExportController::shareFile() AndroidController::instance()->shareConfig(m_config, "amnezia_config"); return; #endif + + 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_config.toUtf8()); + save.close(); + + QFileInfo fi(fileName.toLocalFile()); + QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); } QList ExportController::generateQrCodeImageSeries(const QByteArray &data) diff --git a/client/ui/controllers/exportController.h b/client/ui/controllers/exportController.h index 7af2cd85..84575079 100644 --- a/client/ui/controllers/exportController.h +++ b/client/ui/controllers/exportController.h @@ -36,7 +36,6 @@ public slots: QList getQrCodes(); void saveFile(); - void shareFile(); signals: void generateConfig(int type); diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 218f43cb..5711c3bd 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -41,6 +41,11 @@ namespace } return ConfigTypes::Amnezia; } + +#ifdef Q_OS_ANDROID + ImportController *mInstance = nullptr; + constexpr auto AndroidCameraActivity = "org.amnezia.vpn.qt.CameraActivity"; +#endif } // namespace ImportController::ImportController(const QSharedPointer &serversModel, @@ -49,6 +54,7 @@ ImportController::ImportController(const QSharedPointer &serversMo : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings) { #ifdef Q_OS_ANDROID + mInstance = this; // Set security screen for Android app AndroidUtils::runOnAndroidThreadSync([]() { QJniObject activity = AndroidUtils::getActivity(); @@ -58,6 +64,18 @@ ImportController::ImportController(const QSharedPointer &serversMo window.callMethod("addFlags", "(I)V", FLAG_SECURE); } }); + + AndroidUtils::runOnAndroidThreadAsync([]() { + JNINativeMethod methods[] { + { "passDataToDecoder", "(Ljava/lang/String;)V", reinterpret_cast(onNewQrCodeDataChunk) }, + }; + + QJniObject javaClass(AndroidCameraActivity); + QJniEnvironment env; + jclass objectClass = env->GetObjectClass(javaClass.object()); + env->RegisterNatives(objectClass, methods, sizeof(methods) / sizeof(methods[0])); + env->DeleteLocalRef(objectClass); + }); #endif } @@ -93,11 +111,21 @@ void ImportController::extractConfigFromCode(QString code) m_configFileName = ""; } -void ImportController::extractConfigFromQr() +bool ImportController::extractConfigFromQr(const QByteArray &data) { -#ifdef Q_OS_ANDROID - AndroidController::instance()->startQrReaderActivity(); -#endif + QJsonObject dataObj = QJsonDocument::fromJson(data).object(); + if (!dataObj.isEmpty()) { + m_config = dataObj; + return true; + } + + QByteArray ba_uncompressed = qUncompress(data); + if (!ba_uncompressed.isEmpty()) { + m_config = QJsonDocument::fromJson(ba_uncompressed).object(); + return true; + } + + return false; } QString ImportController::getConfig() @@ -149,21 +177,6 @@ QJsonObject ImportController::extractAmneziaConfig(QString &data) return QJsonDocument::fromJson(ba).object(); } -// bool ImportController::importConnectionFromQr(const QByteArray &data) -//{ -// QJsonObject dataObj = QJsonDocument::fromJson(data).object(); -// if (!dataObj.isEmpty()) { -// return importConnection(dataObj); -// } - -// QByteArray ba_uncompressed = qUncompress(data); -// if (!ba_uncompressed.isEmpty()) { -// return importConnection(QJsonDocument::fromJson(ba_uncompressed).object()); -// } - -// return false; -//} - QJsonObject ImportController::extractOpenVpnConfig(const QString &data) { QJsonObject openVpnConfig; @@ -251,3 +264,90 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data) return config; } + +#ifdef Q_OS_ANDROID +void ImportController::startDecodingQr() +{ + AndroidController::instance()->startQrReaderActivity(); +} + +void ImportController::stopDecodingQr() +{ + QJniObject::callStaticMethod(AndroidCameraActivity, "stopQrCodeReader", "()V"); + emit qrDecodingFinished(); +} + +void ImportController::onNewQrCodeDataChunk(JNIEnv *env, jobject thiz, jstring data) +{ + Q_UNUSED(thiz); + const char *buffer = env->GetStringUTFChars(data, nullptr); + if (!buffer) { + return; + } + + QString parcelBody(buffer); + env->ReleaseStringUTFChars(data, buffer); + + if (mInstance != nullptr) { + if (!mInstance->m_isQrCodeProcessed) { + mInstance->m_qrCodeChunks.clear(); + mInstance->m_isQrCodeProcessed = true; + mInstance->m_totalQrCodeChunksCount = 0; + mInstance->m_receivedQrCodeChunksCount = 0; + } + mInstance->parseQrCodeChunk(parcelBody); + } +} + +void ImportController::parseQrCodeChunk(const QString &code) +{ + // qDebug() << code; + if (!m_isQrCodeProcessed) + return; + + // check if chunk received + QByteArray ba = QByteArray::fromBase64(code.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + QDataStream s(&ba, QIODevice::ReadOnly); + qint16 magic; + s >> magic; + + if (magic == amnezia::qrMagicCode) { + quint8 chunksCount; + s >> chunksCount; + if (m_totalQrCodeChunksCount != chunksCount) { + m_qrCodeChunks.clear(); + } + + m_totalQrCodeChunksCount = chunksCount; + + quint8 chunkId; + s >> chunkId; + s >> m_qrCodeChunks[chunkId]; + m_receivedQrCodeChunksCount = m_qrCodeChunks.size(); + + if (m_qrCodeChunks.size() == m_totalQrCodeChunksCount) { + QByteArray data; + + for (int i = 0; i < m_totalQrCodeChunksCount; ++i) { + data.append(m_qrCodeChunks.value(i)); + } + + bool ok = extractConfigFromQr(data); + if (ok) { + m_isQrCodeProcessed = false; + stopDecodingQr(); + } else { + m_qrCodeChunks.clear(); + m_totalQrCodeChunksCount = 0; + m_receivedQrCodeChunksCount = 0; + } + } + } else { + bool ok = extractConfigFromQr(ba); + if (ok) { + m_isQrCodeProcessed = false; + stopDecodingQr(); + } + } +} +#endif diff --git a/client/ui/controllers/importController.h b/client/ui/controllers/importController.h index 273b12a5..3bdb2252 100644 --- a/client/ui/controllers/importController.h +++ b/client/ui/controllers/importController.h @@ -7,6 +7,9 @@ #include "core/defs.h" #include "ui/models/containers_model.h" #include "ui/models/servers_model.h" +#ifdef Q_OS_ANDROID + #include "jni.h" +#endif class ImportController : public QObject { @@ -21,25 +24,44 @@ public slots: void extractConfigFromFile(); void extractConfigFromData(QString &data); void extractConfigFromCode(QString code); - void extractConfigFromQr(); + bool extractConfigFromQr(const QByteArray &data); QString getConfig(); QString getConfigFileName(); +#if defined Q_OS_ANDROID + void startDecodingQr(); +#endif + signals: void importFinished(); void importErrorOccurred(QString errorMessage); + void qrDecodingFinished(); + private: QJsonObject extractAmneziaConfig(QString &data); QJsonObject extractOpenVpnConfig(const QString &data); QJsonObject extractWireGuardConfig(const QString &data); +#if defined Q_OS_ANDROID + void stopDecodingQr(); + static void onNewQrCodeDataChunk(JNIEnv *env, jobject thiz, jstring data); + void parseQrCodeChunk(const QString &code); +#endif + QSharedPointer m_serversModel; QSharedPointer m_containersModel; std::shared_ptr m_settings; QJsonObject m_config; QString m_configFileName; + +#if defined Q_OS_ANDROID + QMap m_qrCodeChunks; + bool m_isQrCodeProcessed; + int m_totalQrCodeChunksCount; + int m_receivedQrCodeChunksCount; +#endif }; #endif // IMPORTCONTROLLER_H diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/pageController.cpp index 5abeb77f..36d5ba7c 100644 --- a/client/ui/controllers/pageController.cpp +++ b/client/ui/controllers/pageController.cpp @@ -1,10 +1,28 @@ #include "pageController.h" #include +#ifdef Q_OS_ANDROID + #include "../../platforms/android/androidutils.h" + #include +#endif PageController::PageController(const QSharedPointer &serversModel, QObject *parent) : QObject(parent), m_serversModel(serversModel) { +#ifdef Q_OS_ANDROID + // Change color of navigation and status bar's + auto initialPageNavigationBarColor = getInitialPageNavigationBarColor(); + AndroidUtils::runOnAndroidThreadSync([&initialPageNavigationBarColor]() { + QJniObject activity = AndroidUtils::getActivity(); + QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;"); + if (window.isValid()) { + window.callMethod("addFlags", "(I)V", 0x80000000); + window.callMethod("clearFlags", "(I)V", 0x04000000); + window.callMethod("setStatusBarColor", "(I)V", 0xFF0E0E11); + window.callMethod("setNavigationBarColor", "(I)V", initialPageNavigationBarColor); + } + }); +#endif } QString PageController::getInitialPage() @@ -47,3 +65,26 @@ void PageController::keyPressEvent(Qt::Key key) default: return; } } + +unsigned int PageController::getInitialPageNavigationBarColor() +{ + if (m_serversModel->getServersCount()) { + return 0xFF1C1D21; + } else { + return 0xFF0E0E11; + } +} + +void PageController::updateNavigationBarColor(const int color) +{ +#ifdef Q_OS_ANDROID + // Change color of navigation bar + AndroidUtils::runOnAndroidThreadSync([&color]() { + QJniObject activity = AndroidUtils::getActivity(); + QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;"); + if (window.isValid()) { + window.callMethod("setNavigationBarColor", "(I)V", color); + } + }); +#endif +} diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index e8452b45..8087d0fe 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -71,6 +71,9 @@ public slots: void closeWindow(); void keyPressEvent(Qt::Key key); + unsigned int getInitialPageNavigationBarColor(); + void updateNavigationBarColor(const int color); + signals: void goToPageHome(); void goToPageSettings(); diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 692289a8..f133f27a 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -54,10 +54,10 @@ DrawerType { Layout.fillWidth: true Layout.topMargin: 16 - text: Qt.platform.os === "android" ? qsTr("Share") : qsTr("Save connection code") + text: qsTr("Share") onClicked: { - Qt.platform.os === "android" ? ExportController.shareFile() : ExportController.saveFile() + ExportController.saveFile() } } diff --git a/client/ui/qml/Controls2/DrawerType.qml b/client/ui/qml/Controls2/DrawerType.qml index e3c9d588..97fbf034 100644 --- a/client/ui/qml/Controls2/DrawerType.qml +++ b/client/ui/qml/Controls2/DrawerType.qml @@ -12,6 +12,7 @@ Drawer { velocity: 4 } } + exit: Transition { SmoothedAnimation { velocity: 4 @@ -31,4 +32,17 @@ Drawer { Overlay.modal: Rectangle { color: Qt.rgba(14/255, 14/255, 17/255, 0.8) } + + onAboutToShow: { + if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) { + PageController.updateNavigationBarColor(0xFF1C1D21) + } + } + + onClosed: { + var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor() + if (initialPageNavigationBarColor !== 0xFF1C1D21) { + PageController.updateNavigationBarColor(initialPageNavigationBarColor) + } + } } diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index ae24c942..2d6b249d 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -13,6 +13,14 @@ import "../Config" PageType { id: root + Connections { + target: ImportController + + function onQrDecodingFinished() { + goToPage(PageEnum.PageSetupWizardViewConfig) + } + } + FlickableType { id: fl anchors.top: parent.top @@ -77,7 +85,7 @@ It's okay if a friend passed the code.") leftImageSource: "qrc:/images/controls/qr-code.svg" clickedFunction: function() { - ImportController.extractConfigFromQr() + ImportController.startDecodingQr() // goToPage(PageEnum.PageSetupWizardQrReader) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index a0fdf7be..92c95108 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -47,8 +47,7 @@ PageType { } else if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSettings)) { goToPage(PageEnum.PageSettingsServersList, false) } else { - var pagePath = PageController.getPagePath(PageEnum.PageStart) - stackView.replace(pagePath, { "objectName" : pagePath }) + PageController.replaceStartPage() } if (isInstalledContainerFound) { diff --git a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml index e1b93302..372a5cde 100644 --- a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml +++ b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml @@ -30,8 +30,7 @@ PageType { } else if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSettings)) { goToPage(PageEnum.PageSettingsServersList, false) } else { - var pagePath = PageController.getPagePath(PageEnum.PageStart) - stackView.replace(pagePath, { "objectName" : pagePath }) + PageController.replaceStartPage() } } } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 5d49abf7..f522bace 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -1,6 +1,7 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import QtQuick.Shapes import PageEnum 1.0 @@ -81,14 +82,27 @@ PageType { anchors.bottom: parent.bottom topPadding: 8 - bottomPadding: 8//34 + bottomPadding: 8 leftPadding: shareTabButton.visible ? 96 : 128 rightPadding: shareTabButton.visible ? 96 : 128 - background: Rectangle { - border.width: 1 - border.color: "#2C2D30" - color: "#1C1D21" + background: Shape { + width: parent.width + height: parent.height + + ShapePath { + startX: 0 + startY: 0 + + PathLine { x: width; y: 0 } + PathLine { x: width; y: height - 1 } + PathLine { x: 0; y: height - 1 } + PathLine { x: 0; y: 0 } + + strokeWidth: 1 + strokeColor: "#2C2D30" + fillColor: "#1C1D21" + } } TabImageButtonType { diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index d0f9880d..0b89d840 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -50,6 +50,7 @@ Window { while (rootStackView.depth > 1) { rootStackView.pop() } + PageController.updateNavigationBarColor(PageController.getInitialPageNavigationBarColor()) rootStackView.replace(pagePath, { "objectName" : pagePath }) } From 1092abe776a89da2d482c5b6c929e196763c180e Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 31 Jul 2023 00:13:08 +0900 Subject: [PATCH 049/278] added output of notifications/errors after installation/import --- client/protocols/ios_vpnprotocol.mm | 50 ++++++------ client/ui/controllers/exportController.cpp | 38 ++++----- client/ui/controllers/importController.cpp | 4 +- client/ui/controllers/installController.cpp | 80 ++++++++++++++++--- client/ui/controllers/installController.h | 15 +++- client/ui/controllers/pageController.h | 2 +- .../protocolSettingsController.cpp | 15 ---- .../controllers/protocolSettingsController.h | 31 ------- client/ui/controllers/settingsController.cpp | 11 ++- client/ui/controllers/settingsController.h | 3 + client/ui/models/containers_model.cpp | 39 +++++---- client/ui/models/containers_model.h | 11 ++- client/ui/models/protocols_model.cpp | 2 + client/ui/models/protocols_model.h | 1 + client/ui/models/servers_model.cpp | 31 ++++++- client/ui/models/servers_model.h | 7 ++ .../qml/Components/HomeContainersListView.qml | 6 -- .../qml/Components/SelectLanguageDrawer.qml | 2 +- client/ui/qml/Controls2/CheckBoxType.qml | 28 ++++++- .../qml/Controls2/HorizontalRadioButton.qml | 53 ++++++------ client/ui/qml/Controls2/PopupType.qml | 54 ++++++++----- client/ui/qml/Controls2/SwitcherType.qml | 26 ++++-- client/ui/qml/Pages2/PageHome.qml | 34 ++------ .../Pages2/PageProtocolOpenVpnSettings.qml | 5 +- client/ui/qml/Pages2/PageProtocolRaw.qml | 19 ++++- .../ui/qml/Pages2/PageServiceSftpSettings.qml | 4 +- .../Pages2/PageServiceTorWebsiteSettings.qml | 4 +- .../ui/qml/Pages2/PageSettingsApplication.qml | 1 + client/ui/qml/Pages2/PageSettingsBackup.qml | 18 +++++ client/ui/qml/Pages2/PageSettingsDns.qml | 6 ++ .../ui/qml/Pages2/PageSettingsServerData.qml | 39 ++++++--- .../ui/qml/Pages2/PageSettingsServerInfo.qml | 1 + .../qml/Pages2/PageSettingsServerProtocol.qml | 32 +++++--- .../qml/Pages2/PageSetupWizardInstalling.qml | 15 +--- .../PageSetupWizardProtocolSettings.qml | 9 ++- client/ui/qml/Pages2/PageSetupWizardStart.qml | 17 ---- client/ui/qml/Pages2/PageShare.qml | 3 +- client/ui/qml/Pages2/PageStart.qml | 28 ++----- client/ui/qml/main2.qml | 47 +++++++++++ 39 files changed, 488 insertions(+), 303 deletions(-) delete mode 100644 client/ui/controllers/protocolSettingsController.cpp delete mode 100644 client/ui/controllers/protocolSettingsController.h diff --git a/client/protocols/ios_vpnprotocol.mm b/client/protocols/ios_vpnprotocol.mm index f71db34a..2195dbdd 100644 --- a/client/protocols/ios_vpnprotocol.mm +++ b/client/protocols/ios_vpnprotocol.mm @@ -136,7 +136,7 @@ void IOSVpnProtocol::stop() [m_controller disconnect]; - emit connectionStateChanged(Disconnected); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); [m_controller dealloc]; m_controller = nullptr; @@ -264,7 +264,7 @@ void IOSVpnProtocol::setupWireguardProtocol(const QJsonObject& rawConfig) [m_controller dealloc]; m_controller = nullptr; dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Error); + emit connectionStateChanged(Vpn::ConnectionState::Error); m_isChangingState = false; }); return; @@ -272,7 +272,7 @@ void IOSVpnProtocol::setupWireguardProtocol(const QJsonObject& rawConfig) case ConnectionStateConnected: { Q_ASSERT(date); dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Connected); + emit connectionStateChanged(Vpn::ConnectionState::Connected); m_isChangingState = false; }); return; @@ -280,7 +280,7 @@ void IOSVpnProtocol::setupWireguardProtocol(const QJsonObject& rawConfig) case ConnectionStateDisconnected: [m_controller disconnect]; dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Disconnected); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); m_isChangingState = false; }); return; @@ -294,13 +294,13 @@ void IOSVpnProtocol::setupWireguardProtocol(const QJsonObject& rawConfig) qDebug() << "State changed: " << a_connected; if (a_connected) { dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Connected); + emit connectionStateChanged(Vpn::ConnectionState::Connected); m_isChangingState = false; }); return; } dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Disconnected); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); m_isChangingState = false; }); }]; @@ -325,7 +325,7 @@ void IOSVpnProtocol::setupCloakProtocol(const QJsonObject &rawConfig) [m_controller dealloc]; m_controller = nullptr; dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Error); + emit connectionStateChanged(Vpn::ConnectionState::Error); m_isChangingState = false; }); return; @@ -333,7 +333,7 @@ void IOSVpnProtocol::setupCloakProtocol(const QJsonObject &rawConfig) case ConnectionStateConnected: { Q_ASSERT(date); dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Connected); + emit connectionStateChanged(Vpn::ConnectionState::Connected); m_isChangingState = false; }); return; @@ -341,7 +341,7 @@ void IOSVpnProtocol::setupCloakProtocol(const QJsonObject &rawConfig) case ConnectionStateDisconnected: // Just in case we are connecting, let's call disconnect. dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Disconnected); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); m_isChangingState = false; }); return; @@ -355,13 +355,13 @@ void IOSVpnProtocol::setupCloakProtocol(const QJsonObject &rawConfig) qDebug() << "VPN State changed: " << a_connected; if (a_connected) { dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Connected); + emit connectionStateChanged(Vpn::ConnectionState::Connected); m_isChangingState = false; }); return; } dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Disconnected); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); m_isChangingState = false; }); }]; @@ -388,7 +388,7 @@ void IOSVpnProtocol::setupOpenVPNProtocol(const QJsonObject &rawConfig) [m_controller dealloc]; m_controller = nullptr; dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Error); + emit connectionStateChanged(Vpn::ConnectionState::Error); m_isChangingState = false; }); return; @@ -396,7 +396,7 @@ void IOSVpnProtocol::setupOpenVPNProtocol(const QJsonObject &rawConfig) case ConnectionStateConnected: { Q_ASSERT(date); dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Connected); + emit connectionStateChanged(Vpn::ConnectionState::Connected); m_isChangingState = false; }); return; @@ -404,7 +404,7 @@ void IOSVpnProtocol::setupOpenVPNProtocol(const QJsonObject &rawConfig) case ConnectionStateDisconnected: // Just in case we are connecting, let's call disconnect. dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Disconnected); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); m_isChangingState = false; }); return; @@ -418,13 +418,13 @@ void IOSVpnProtocol::setupOpenVPNProtocol(const QJsonObject &rawConfig) qDebug() << "VPN State changed: " << a_connected; if (a_connected) { dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Connected); + emit connectionStateChanged(Vpn::ConnectionState::Connected); m_isChangingState = false; }); return; } dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Disconnected); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); m_isChangingState = false; }); }]; @@ -452,7 +452,7 @@ void IOSVpnProtocol::setupShadowSocksProtocol(const QJsonObject &rawConfig) [m_controller dealloc]; m_controller = nullptr; dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Error); + emit connectionStateChanged(Vpn::ConnectionState::Error); m_isChangingState = false; }); return; @@ -460,7 +460,7 @@ void IOSVpnProtocol::setupShadowSocksProtocol(const QJsonObject &rawConfig) case ConnectionStateConnected: { Q_ASSERT(date); dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Connected); + emit connectionStateChanged(Vpn::ConnectionState::Connected); m_isChangingState = false; }); return; @@ -468,7 +468,7 @@ void IOSVpnProtocol::setupShadowSocksProtocol(const QJsonObject &rawConfig) case ConnectionStateDisconnected: // Just in case we are connecting, let's call disconnect. dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(VpnConnectionState::Disconnected); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); m_isChangingState = false; }); return; @@ -482,13 +482,13 @@ void IOSVpnProtocol::setupShadowSocksProtocol(const QJsonObject &rawConfig) qDebug() << "SS State changed: " << a_connected; if (a_connected) { dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Connected); + emit connectionStateChanged(Vpn::ConnectionState::Connected); m_isChangingState = false; }); return; } dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Disconnected); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); m_isChangingState = false; }); }]; @@ -539,7 +539,7 @@ void IOSVpnProtocol::launchWireguardTunnel(const QJsonObject &rawConfig) failureCallback:^() { qDebug() << "Wireguard Protocol - connection failed"; dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Disconnected); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); m_isChangingState = false; }); }]; @@ -590,7 +590,7 @@ void IOSVpnProtocol::launchCloakTunnel(const QJsonObject &rawConfig) failureCallback:^{ qDebug() << "IOSVPNProtocol (OpenVPN Cloak) - connection failed"; dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Disconnected); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); m_isChangingState = false; }); }]; @@ -607,7 +607,7 @@ void IOSVpnProtocol::launchOpenVPNTunnel(const QJsonObject &rawConfig) failureCallback:^{ qDebug() << "IOSVPNProtocol (OpenVPN) - connection failed"; dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Disconnected); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); m_isChangingState = false; }); }]; @@ -624,7 +624,7 @@ void IOSVpnProtocol::launchShadowSocksTunnel(const QJsonObject &rawConfig) { failureCallback:^{ qDebug() << "IOSVPNProtocol (ShadowSocks) - connection failed"; dispatch_async(dispatch_get_main_queue(), ^{ - emit connectionStateChanged(Disconnected); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); m_isChangingState = false; }); }]; diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index 6e4abb1f..8fbd69f1 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -204,25 +204,25 @@ QList ExportController::getQrCodes() void ExportController::saveFile() { #if defined Q_OS_IOS - ext.replace("*", ""); - QString fileName = QDir::tempPath() + "/" + suggestedName; - - if (fileName.isEmpty()) - return; - if (!fileName.endsWith(ext)) - fileName.append(ext); - - QFile::remove(fileName); - - QFile save(fileName); - save.open(QIODevice::WriteOnly); - save.write(data.toUtf8()); - save.close(); - - QStringList filesToSend; - filesToSend.append(fileName); - MobileUtils::shareText(filesToSend); - return; +// ext.replace("*", ""); +// QString fileName = QDir::tempPath() + "/" + suggestedName; +// +// if (fileName.isEmpty()) +// return; +// if (!fileName.endsWith(ext)) +// fileName.append(ext); +// +// QFile::remove(fileName); +// +// QFile save(fileName); +// save.open(QIODevice::WriteOnly); +// save.write(data.toUtf8()); +// save.close(); +// +// QStringList filesToSend; +// filesToSend.append(fileName); +// MobileUtils::shareText(filesToSend); +// return; #endif #if defined Q_OS_ANDROID AndroidController::instance()->shareConfig(m_config, "amnezia_config"); diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 5711c3bd..8e2ad5d1 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -149,9 +149,7 @@ void ImportController::importConfig() if (credentials.isValid() || m_config.contains(config_key::containers)) { m_serversModel->addServer(m_config); - if (!m_config.value(config_key::containers).toArray().isEmpty()) { - m_serversModel->setDefaultServerIndex(m_serversModel->getServersCount() - 1); - } + m_serversModel->setDefaultServerIndex(m_serversModel->getServersCount() - 1); emit importFinished(); } else { diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 68e47a89..be235e42 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -88,14 +88,20 @@ void InstallController::installServer(DockerContainer container, QJsonObject &co QMap installedContainers; ErrorCode errorCode = serverController.getAlreadyInstalledContainers(m_currentlyInstalledServerCredentials, installedContainers); + + QString finishMessage = ""; + if (!installedContainers.contains(container)) { errorCode = serverController.setupContainer(m_currentlyInstalledServerCredentials, container, config); installedContainers.insert(container, config); + finishMessage = ContainerProps::containerHumanNames().value(container) + tr(" installed successfully. "); + } else { + finishMessage = + ContainerProps::containerHumanNames().value(container) + tr(" is already installed on the server. "); } - - bool isInstalledContainerFound = false; - if (!installedContainers.isEmpty()) { - isInstalledContainerFound = true; + if (installedContainers.size() > 1) { + finishMessage += tr("\nAlready installed containers were found on the server. " + "All installed containers have been added to the application"); } if (errorCode == ErrorCode::NoError) { @@ -117,7 +123,7 @@ void InstallController::installServer(DockerContainer container, QJsonObject &co m_serversModel->addServer(server); m_serversModel->setDefaultServerIndex(m_serversModel->getServersCount() - 1); - emit installServerFinished(false); // todo incorrect notification about found containers + emit installServerFinished(finishMessage); return; } @@ -135,16 +141,19 @@ void InstallController::installContainer(DockerContainer container, QJsonObject QMap installedContainers; ErrorCode errorCode = serverController.getAlreadyInstalledContainers(serverCredentials, installedContainers); - bool isInstalledContainerFound = false; - if (!installedContainers.isEmpty()) { - isInstalledContainerFound = true; - } + QString finishMessage = ""; if (!installedContainers.contains(container)) { errorCode = serverController.setupContainer(serverCredentials, container, config); installedContainers.insert(container, config); + finishMessage = ContainerProps::containerHumanNames().value(container) + tr(" installed successfully. "); + } else { + finishMessage = + ContainerProps::containerHumanNames().value(container) + tr(" is already installed on the server. "); } + bool isInstalledContainerAddedToGui = false; + if (errorCode == ErrorCode::NoError) { for (auto iterator = installedContainers.begin(); iterator != installedContainers.end(); iterator++) { auto modelIndex = m_containersModel->index(iterator.key()); @@ -153,10 +162,17 @@ void InstallController::installContainer(DockerContainer container, QJsonObject if (containerConfig.isEmpty()) { m_containersModel->setData(m_containersModel->index(iterator.key()), iterator.value(), ContainersModel::Roles::ConfigRole); + if (container != iterator.key()) { // skip the newly installed container + isInstalledContainerAddedToGui = true; + } } } + if (isInstalledContainerAddedToGui) { + finishMessage += tr("\nAlready installed containers were found on the server. " + "All installed containers have been added to the application"); + } - emit installContainerFinished(false); // todo incorrect notification about found containers + emit installContainerFinished(finishMessage); return; } @@ -233,11 +249,55 @@ void InstallController::updateContainer(QJsonObject config) emit installationErrorOccurred(errorString(errorCode)); } +void InstallController::removeCurrentlyProcessedServer() +{ + int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + QString serverName = m_serversModel->data(serverIndex, ServersModel::Roles::NameRole).toString(); + + m_serversModel->removeServer(); + emit removeCurrentlyProcessedServerFinished(tr("Server '") + serverName + tr("' was removed")); +} + +void InstallController::removeAllContainers() +{ + int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + QString serverName = m_serversModel->data(serverIndex, ServersModel::Roles::NameRole).toString(); + + ErrorCode errorCode = m_containersModel->removeAllContainers(); + if (errorCode == ErrorCode::NoError) { + emit removeAllContainersFinished(tr("All containers from server '") + serverName + ("' have been removed")); + return; + } + emit installationErrorOccurred(errorString(errorCode)); +} + +void InstallController::removeCurrentlyProcessedContainer() +{ + int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); + QString serverName = m_serversModel->data(serverIndex, ServersModel::Roles::NameRole).toString(); + + int container = m_containersModel->getCurrentlyProcessedContainerIndex(); + QString containerName = m_containersModel->data(container, ContainersModel::Roles::NameRole).toString(); + + ErrorCode errorCode = m_containersModel->removeCurrentlyProcessedContainer(); + if (errorCode == ErrorCode::NoError) { + emit removeCurrentlyProcessedContainerFinished(containerName + tr(" has been removed from the server '") + + serverName + "'"); + return; + } + emit installationErrorOccurred(errorString(errorCode)); +} + QRegularExpression InstallController::ipAddressPortRegExp() { return Utils::ipAddressPortRegExp(); } +QRegularExpression InstallController::ipAddressRegExp() +{ + return Utils::ipAddressRegExp(); +} + void InstallController::setCurrentlyInstalledServerCredentials(const QString &hostName, const QString &userName, const QString &secretData) { diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index fdd0ebed..8bc04f39 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -28,18 +28,27 @@ public slots: void updateContainer(QJsonObject config); + void removeCurrentlyProcessedServer(); + void removeAllContainers(); + void removeCurrentlyProcessedContainer(); + QRegularExpression ipAddressPortRegExp(); + QRegularExpression ipAddressRegExp(); void mountSftpDrive(const QString &port, const QString &password, const QString &username); signals: - void installContainerFinished(bool isInstalledContainerFound); - void installServerFinished(bool isInstalledContainerFound); + void installContainerFinished(QString finishMessage); + void installServerFinished(QString finishMessage); void updateContainerFinished(); void scanServerFinished(bool isInstalledContainerFound); + void removeCurrentlyProcessedServerFinished(QString finishedMessage); + void removeAllContainersFinished(QString finishedMessage); + void removeCurrentlyProcessedContainerFinished(QString finishedMessage); + void installationErrorOccurred(QString errorMessage); void serverAlreadyExists(int serverIndex); @@ -57,7 +66,9 @@ private: bool m_shouldCreateServer; +#ifndef Q_OS_IOS QList> m_sftpMountProcesses; +#endif }; #endif // INSTALLCONTROLLER_H diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 8087d0fe..4273ed25 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -84,7 +84,7 @@ signals: void replaceStartPage(); void showErrorMessage(QString errorMessage); - void showInfoMessage(QString message); + void showNotificationMessage(QString message); void showBusyIndicator(bool visible); diff --git a/client/ui/controllers/protocolSettingsController.cpp b/client/ui/controllers/protocolSettingsController.cpp deleted file mode 100644 index 11b6a904..00000000 --- a/client/ui/controllers/protocolSettingsController.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include "protocolSettingsController.h" - -ProtocolSettingsController::ProtocolSettingsController(const QSharedPointer &serversModel, - const QSharedPointer &containersModel, - const std::shared_ptr &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); - return QByteArray(); -} diff --git a/client/ui/controllers/protocolSettingsController.h b/client/ui/controllers/protocolSettingsController.h deleted file mode 100644 index 730cbda7..00000000 --- a/client/ui/controllers/protocolSettingsController.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef PROTOCOLSETTINGSCONTROLLER_H -#define PROTOCOLSETTINGSCONTROLLER_H - -#include - -#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, - const QSharedPointer &containersModel, - const std::shared_ptr &settings, - QObject *parent = nullptr); - -public slots: - QByteArray getOpenVpnConfig(); - -signals: - -private: - QSharedPointer m_serversModel; - QSharedPointer m_containersModel; - std::shared_ptr m_settings; -}; - -#endif // PROTOCOLSETTINGSCONTROLLER_H diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index b501d085..eda67919 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -84,9 +84,10 @@ void SettingsController::restoreAppConfig() Utils::getFileName(Q_NULLPTR, tr("Open backup"), QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.backup"); - // todo error processing - if (fileName.isEmpty()) + if (fileName.isEmpty()) { + emit changeSettingsErrorOccurred(tr("Backup file is empty")); return; + } QFile file(fileName); file.open(QIODevice::ReadOnly); @@ -94,7 +95,10 @@ void SettingsController::restoreAppConfig() bool ok = m_settings->restoreAppConfig(data); if (ok) { - // emit uiLogic()->showWarningMessage(tr("Can't import config, file is corrupted.")); + m_serversModel->resetModel(); + emit restoreBackupFinished(); + } else { + emit changeSettingsErrorOccurred(tr("Backup file is corrupted")); } } @@ -106,4 +110,5 @@ QString SettingsController::getAppVersion() void SettingsController::clearSettings() { m_settings->clearSettings(); + m_serversModel->resetModel(); } diff --git a/client/ui/controllers/settingsController.h b/client/ui/controllers/settingsController.h index 313f934d..ac85d300 100644 --- a/client/ui/controllers/settingsController.h +++ b/client/ui/controllers/settingsController.h @@ -47,6 +47,9 @@ signals: void secondaryDnsChanged(); void loggingStateChanged(); + void restoreBackupFinished(); + void changeSettingsErrorOccurred(QString errorMessage); + private: QSharedPointer m_serversModel; QSharedPointer m_containersModel; diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index 922542dd..ea571a22 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -83,6 +83,12 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const return QVariant(); } +QVariant ContainersModel::data(const int index, int role) const +{ + QModelIndex modelIndex = this->index(index); + return data(modelIndex, role); +} + void ContainersModel::setCurrentlyProcessedServerIndex(const int index) { beginResetModel(); @@ -123,11 +129,12 @@ QJsonObject ContainersModel::getCurrentlyProcessedContainerConfig() return qvariant_cast(data(index(m_currentlyProcessedContainerIndex), ConfigRole)); } -void ContainersModel::removeAllContainers() +ErrorCode ContainersModel::removeAllContainers() { ServerController serverController(m_settings); - auto errorCode = serverController.removeAllContainers(m_settings->serverCredentials(m_currentlyProcessedServerIndex)); + ErrorCode errorCode = + serverController.removeAllContainers(m_settings->serverCredentials(m_currentlyProcessedServerIndex)); if (errorCode == ErrorCode::NoError) { beginResetModel(); @@ -138,30 +145,32 @@ void ContainersModel::removeAllContainers() setData(index(DockerContainer::None, 0), true, IsDefaultRole); endResetModel(); } - - // todo process errors + return errorCode; } -void ContainersModel::removeCurrentlyProcessedContainer() +ErrorCode ContainersModel::removeCurrentlyProcessedContainer() { ServerController serverController(m_settings); auto credentials = m_settings->serverCredentials(m_currentlyProcessedServerIndex); auto dockerContainer = static_cast(m_currentlyProcessedContainerIndex); - ErrorCode e = serverController.removeContainer(credentials, dockerContainer); + ErrorCode errorCode = serverController.removeContainer(credentials, dockerContainer); - beginResetModel(); // todo change to begin remove rows? - m_settings->removeContainerConfig(m_currentlyProcessedServerIndex, dockerContainer); - m_containers = m_settings->containers(m_currentlyProcessedServerIndex); + if (errorCode == ErrorCode::NoError) { + beginResetModel(); // todo change to begin remove rows? + m_settings->removeContainerConfig(m_currentlyProcessedServerIndex, dockerContainer); + m_containers = m_settings->containers(m_currentlyProcessedServerIndex); - if (m_defaultContainerIndex == m_currentlyProcessedContainerIndex) { - if (m_containers.isEmpty()) { - setData(index(DockerContainer::None, 0), true, IsDefaultRole); - } else { - setData(index(m_containers.begin().key(), 0), true, IsDefaultRole); + if (m_defaultContainerIndex == m_currentlyProcessedContainerIndex) { + if (m_containers.isEmpty()) { + setData(index(DockerContainer::None, 0), true, IsDefaultRole); + } else { + setData(index(m_containers.begin().key(), 0), true, IsDefaultRole); + } } + endResetModel(); } - endResetModel(); + return errorCode; } void ContainersModel::clearCachedProfiles() diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index 75ba9114..690eff41 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -36,9 +36,9 @@ public: bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QVariant data(const int index, int role) const; -signals: - void defaultContainerChanged(); + Q_PROPERTY(QString defaultContainerName READ getDefaultContainerName NOTIFY defaultContainerChanged) public slots: DockerContainer getDefaultContainer(); @@ -52,8 +52,8 @@ public slots: QString getCurrentlyProcessedContainerName(); QJsonObject getCurrentlyProcessedContainerConfig(); - void removeAllContainers(); - void removeCurrentlyProcessedContainer(); + ErrorCode removeAllContainers(); + ErrorCode removeCurrentlyProcessedContainer(); void clearCachedProfiles(); bool isAmneziaDnsContainerInstalled(); @@ -62,6 +62,9 @@ public slots: protected: QHash roleNames() const override; +signals: + void defaultContainerChanged(); + private: QMap m_containers; diff --git a/client/ui/models/protocols_model.cpp b/client/ui/models/protocols_model.cpp index da730f55..8c999470 100644 --- a/client/ui/models/protocols_model.cpp +++ b/client/ui/models/protocols_model.cpp @@ -17,6 +17,7 @@ QHash ProtocolsModel::roleNames() const roles[ProtocolNameRole] = "protocolName"; roles[ProtocolPageRole] = "protocolPage"; + roles[ProtocolIndexRole] = "protocolIndex"; roles[RawConfigRole] = "rawConfig"; return roles; @@ -35,6 +36,7 @@ QVariant ProtocolsModel::data(const QModelIndex &index, int role) const } case ProtocolPageRole: return static_cast(protocolPage(ProtocolProps::protoFromString(m_content.keys().at(index.row())))); + case ProtocolIndexRole: return ProtocolProps::protoFromString(m_content.keys().at(index.row())); case RawConfigRole: { auto protocolConfig = m_content.value(ContainerProps::containerTypeToString(m_container)).toObject(); auto lastConfigJsonDoc = diff --git a/client/ui/models/protocols_model.h b/client/ui/models/protocols_model.h index c4ad5c70..5ee8a3dd 100644 --- a/client/ui/models/protocols_model.h +++ b/client/ui/models/protocols_model.h @@ -14,6 +14,7 @@ public: enum Roles { ProtocolNameRole = Qt::UserRole + 1, ProtocolPageRole, + ProtocolIndexRole, RawConfigRole }; diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index fabbb829..364fd71f 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -78,6 +78,15 @@ QVariant ServersModel::data(const int index, int role) const return data(modelIndex, role); } +void ServersModel::resetModel() +{ + beginResetModel(); + m_servers = m_settings->serversArray(); + m_defaultServerIndex = m_settings->defaultServerIndex(); + m_currentlyProcessedServerIndex = m_defaultServerIndex; + endResetModel(); +} + void ServersModel::setDefaultServerIndex(const int index) { m_settings->setDefaultServer(index); @@ -90,11 +99,31 @@ const int ServersModel::getDefaultServerIndex() return m_defaultServerIndex; } +const QString ServersModel::getDefaultServerName() +{ + return qvariant_cast(data(m_defaultServerIndex, NameRole)); +} + +const QString ServersModel::getDefaultServerHostName() +{ + return qvariant_cast(data(m_defaultServerIndex, HostNameRole)); +} + const int ServersModel::getServersCount() { return m_servers.count(); } +bool ServersModel::hasServerWithWriteAccess() +{ + for (size_t i = 0; i < getServersCount(); i++) { + if (qvariant_cast(data(i, HasWriteAccessRole))) { + return true; + } + } + return false; +} + void ServersModel::setCurrentlyProcessedServerIndex(const int index) { m_currentlyProcessedServerIndex = index; @@ -123,7 +152,7 @@ bool ServersModel::isCurrentlyProcessedServerHasWriteAccess() bool ServersModel::isDefaultServerHasWriteAccess() { - return qvariant_cast(data(m_currentlyProcessedServerIndex, HasWriteAccessRole)); + return qvariant_cast(data(m_defaultServerIndex, HasWriteAccessRole)); } void ServersModel::addServer(const QJsonObject &server) diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index a31e3c04..60ec35b2 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -28,17 +28,24 @@ public: QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant data(const int index, int role = Qt::DisplayRole) const; + void resetModel(); + Q_PROPERTY(int defaultIndex READ getDefaultServerIndex WRITE setDefaultServerIndex NOTIFY defaultServerIndexChanged) + Q_PROPERTY(QString defaultServerName READ getDefaultServerName NOTIFY defaultServerIndexChanged) + Q_PROPERTY(QString defaultServerHostName READ getDefaultServerHostName NOTIFY defaultServerIndexChanged) Q_PROPERTY(int currentlyProcessedIndex READ getCurrentlyProcessedServerIndex WRITE setCurrentlyProcessedServerIndex NOTIFY currentlyProcessedServerIndexChanged) public slots: void setDefaultServerIndex(const int index); const int getDefaultServerIndex(); + const QString getDefaultServerName(); + const QString getDefaultServerHostName(); bool isDefaultServerCurrentlyProcessed(); bool isCurrentlyProcessedServerHasWriteAccess(); bool isDefaultServerHasWriteAccess(); + bool hasServerWithWriteAccess(); const int getServersCount(); diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index 28f81b2e..0c2408be 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -78,11 +78,5 @@ ListView { Layout.fillWidth: true } } - - Component.onCompleted: { - if (isDefault) { - root.currentContainerName = name - } - } } } diff --git a/client/ui/qml/Components/SelectLanguageDrawer.qml b/client/ui/qml/Components/SelectLanguageDrawer.qml index f1ffa416..d872a889 100644 --- a/client/ui/qml/Components/SelectLanguageDrawer.qml +++ b/client/ui/qml/Components/SelectLanguageDrawer.qml @@ -67,7 +67,7 @@ DrawerType { delegate: Item { implicitWidth: root.width - implicitHeight: content.implicitHeight + implicitHeight: delegateContent.implicitHeight ColumnLayout { id: delegateContent diff --git a/client/ui/qml/Controls2/CheckBoxType.qml b/client/ui/qml/Controls2/CheckBoxType.qml index e4b1703f..2724a30c 100644 --- a/client/ui/qml/Controls2/CheckBoxType.qml +++ b/client/ui/qml/Controls2/CheckBoxType.qml @@ -9,6 +9,11 @@ CheckBox { id: root property string descriptionText + property string descriptionTextColor: "#878B91" + property string descriptionTextDisabledColor: "#494B50" + + property string textColor: "#D7D8DB" + property string textDisabledColor: "#878B91" property string hoveredColor: Qt.rgba(1, 1, 1, 0.05) property string defaultColor: "transparent" @@ -16,14 +21,16 @@ CheckBox { property string defaultBorderColor: "#D7D8DB" property string checkedBorderColor: "#FBB26A" + property string checkedBorderDisabledColor: "#5A330C" property string checkedImageColor: "#FBB26A" property string pressedImageColor: "#A85809" property string defaultImageColor: "transparent" + property string checkedDisabledImageColor: "#84603D" property string imageSource: "qrc:/images/controls/check.svg" - hoverEnabled: true + hoverEnabled: enabled ? true : false indicator: Rectangle { id: background @@ -52,7 +59,7 @@ CheckBox { width: 24 height: 24 color: "transparent" - border.color: root.checked ? checkedBorderColor : defaultBorderColor + border.color: root.checked ? (root.enabled ? checkedBorderColor : checkedBorderDisabledColor) : defaultBorderColor border.width: 1 radius: 4 @@ -63,7 +70,19 @@ CheckBox { layer { enabled: true effect: ColorOverlay { - color: root.pressed ? pressedImageColor : root.checked ? checkedImageColor : defaultImageColor + color: { + if (root.pressed) { + return root.pressedImageColor + } else if (root.checked) { + if (root.enabled) { + return root.checkedImageColor + } else { + return root.checkedDisabledImageColor + } + } else { + return root.defaultImageColor + } + } } } } @@ -90,6 +109,7 @@ CheckBox { Layout.fillWidth: true text: root.text + color: root.enabled ? root.textColor : root.textDisabledColor } CaptionTextType { @@ -98,7 +118,7 @@ CheckBox { Layout.fillWidth: true text: root.descriptionText - color: "#878b91" + color: root.enabled ? root.descriptionTextColor : root.descriptionTextDisabledColor visible: root.descriptionText !== "" } diff --git a/client/ui/qml/Controls2/HorizontalRadioButton.qml b/client/ui/qml/Controls2/HorizontalRadioButton.qml index 88fc2531..1ac5840a 100644 --- a/client/ui/qml/Controls2/HorizontalRadioButton.qml +++ b/client/ui/qml/Controls2/HorizontalRadioButton.qml @@ -9,14 +9,16 @@ RadioButton { property string hoveredColor: Qt.rgba(1, 1, 1, 0.05) property string defaultColor: Qt.rgba(1, 1, 1, 0) - property string disabledColor: Qt.rgba(1, 1, 1, 0) - property string selectedColor: Qt.rgba(1, 1, 1, 0) + property string checkedColor: Qt.rgba(1, 1, 1, 0) + property string disabledColor: "transparent" - property string textColor: "#0E0E11" + property string textColor: "#D7D8DB" + property string textDisabledColor: "#878B91" property string pressedBorderColor: "#494B50" - property string selectedBorderColor: "#FBB26A" + property string checkedBorderColor: "#FBB26A" property string defaultBodredColor: "transparent" + property string checkedDisabledBorderColor: "#84603D" property int borderWidth: 0 implicitWidth: content.implicitWidth @@ -27,39 +29,39 @@ RadioButton { radius: 16 color: { -// if (root.enabled) { + if (root.enabled) { if (root.hovered) { - return hoveredColor + return root.hoveredColor } else if (root.checked) { - return selectedColor + return root.checkedColor } - return defaultColor -// } else { -// return disabledColor -// } + return root.defaultColor + } else { + return root.disabledColor + } } border.color: { -// if (root.enabled) { + if (root.enabled) { if (root.pressed) { - return pressedBorderColor + return root.pressedBorderColor } else if (root.checked) { - return selectedBorderColor + return root.checkedBorderColor } - return defaultBodredColor -// } -// return defaultBodredColor + return root.defaultBodredColor + } else { + if (root.checked) { + return root.checkedDisabledBorderColor + } + return root.defaultBodredColor + } } border.width: { -// if (root.enabled) { - if(root.checked) { - return 1 - } - return root.pressed ? 1 : 0 -// } else { -// return 0 -// } + if(root.checked) { + return 1 + } + return root.pressed ? 1 : 0 } Behavior on color { @@ -77,6 +79,7 @@ RadioButton { ButtonTextType { text: root.text + color: root.enabled ? root.textColor : root.textDisabledColor Layout.fillWidth: true Layout.rightMargin: 16 diff --git a/client/ui/qml/Controls2/PopupType.qml b/client/ui/qml/Controls2/PopupType.qml index 61bdfd18..e7bb16f4 100644 --- a/client/ui/qml/Controls2/PopupType.qml +++ b/client/ui/qml/Controls2/PopupType.qml @@ -7,7 +7,7 @@ import "TextTypes" Popup { id: root - property string popupErrorMessageText + property string text property bool closeButtonVisible: true leftMargin: 25 @@ -17,46 +17,56 @@ Popup { width: parent.width - leftMargin - rightMargin anchors.centerIn: parent - modal: true + modal: root.closeButtonVisible closePolicy: Popup.CloseOnEscape Overlay.modal: Rectangle { + visible: root.closeButtonVisible color: Qt.rgba(14/255, 14/255, 17/255, 0.8) } background: Rectangle { anchors.fill: parent - color: "white"//Qt.rgba(215/255, 216/255, 219/255, 0.95) + color: "white" radius: 4 } - contentItem: RowLayout { + contentItem: Item { + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight + anchors.fill: parent - anchors.leftMargin: 16 - anchors.rightMargin: 16 - CaptionTextType { - horizontalAlignment: Text.AlignLeft - Layout.fillWidth: true + RowLayout { + id: content - text: root.popupErrorMessageText - } + anchors.fill: parent + anchors.leftMargin: 16 + anchors.rightMargin: 16 - BasicButtonType { - visible: closeButtonVisible + CaptionTextType { + horizontalAlignment: Text.AlignLeft + Layout.fillWidth: true - defaultColor: "white"//"transparent"//Qt.rgba(215/255, 216/255, 219/255, 0.95) - hoveredColor: "#C1C2C5" - pressedColor: "#AEB0B7" - disabledColor: "#494B50" + text: root.text + } - textColor: "#0E0E11" - borderWidth: 0 + BasicButtonType { + visible: closeButtonVisible - text: qsTr("Close") - onClicked: { - root.close() + defaultColor: "white" + hoveredColor: "#C1C2C5" + pressedColor: "#AEB0B7" + disabledColor: "#494B50" + + textColor: "#0E0E11" + borderWidth: 0 + + text: qsTr("Close") + onClicked: { + root.close() + } } } } diff --git a/client/ui/qml/Controls2/SwitcherType.qml b/client/ui/qml/Controls2/SwitcherType.qml index b63c64fb..aca5ba0b 100644 --- a/client/ui/qml/Controls2/SwitcherType.qml +++ b/client/ui/qml/Controls2/SwitcherType.qml @@ -8,14 +8,24 @@ Switch { id: root property alias descriptionText: description.text + property string descriptionTextColor: "#878B91" + property string descriptionTextDisabledColor: "#494B50" + + property string textColor: "#D7D8DB" + property string textDisabledColor: "#878B91" property string checkedIndicatorColor: "#412102" property string defaultIndicatorColor: "transparent" + property string checkedDisabledIndicatorColor: "#5A330C" + property string checkedIndicatorBorderColor: "#412102" property string defaultIndicatorBorderColor: "#494B50" + property string checkedDisabledIndicatorBorderColor: "#5A330C" property string checkedInnerCircleColor: "#FBB26A" property string defaultInnerCircleColor: "#D7D8DB" + property string checkedDisabledInnerCircleColor: "#84603D" + property string defaultDisabledInnerCircleColor: "#494B50" property string hoveredIndicatorBackgroundColor: Qt.rgba(1, 1, 1, 0.08) property string defaultIndicatorBackgroundColor: "transparent" @@ -23,6 +33,8 @@ Switch { implicitWidth: content.implicitWidth + switcher.implicitWidth implicitHeight: content.implicitHeight + hoverEnabled: enabled ? true : false + indicator: Rectangle { id: switcher @@ -33,8 +45,10 @@ Switch { implicitHeight: 32 radius: 16 - color: root.checked ? checkedIndicatorColor : defaultIndicatorColor - border.color: root.checked ? checkedIndicatorBorderColor : defaultIndicatorBorderColor + color: root.checked ? (root.enabled ? root.checkedIndicatorColor : root.checkedDisabledIndicatorColor) + : root.defaultIndicatorColor + border.color: root.checked ? (root.enabled ? root.checkedIndicatorBorderColor : root.checkedDisabledIndicatorBorderColor) + : root.defaultIndicatorBorderColor Behavior on color { PropertyAnimation { duration: 200 } @@ -51,7 +65,8 @@ Switch { width: root.checked ? 24 : 16 height: root.checked ? 24 : 16 radius: 23 - color: root.checked ? checkedInnerCircleColor : defaultInnerCircleColor + color: root.checked ? (root.enabled ? root.checkedInnerCircleColor : root.checkedDisabledInnerCircleColor) + : (root.enabled ? root.defaultInnerCircleColor : root.defaultDisabledInnerCircleColor) Behavior on x { PropertyAnimation { duration: 200 } @@ -63,7 +78,7 @@ Switch { width: 40 height: 40 radius: 23 - color: hovered ? hoveredIndicatorBackgroundColor : defaultIndicatorBackgroundColor + color: root.hovered ? root.hoveredIndicatorBackgroundColor : root.defaultIndicatorBackgroundColor Behavior on color { PropertyAnimation { duration: 200 } @@ -81,6 +96,7 @@ Switch { Layout.fillWidth: true text: root.text + color: root.enabled ? root.textColor : root.textDisabledColor } CaptionTextType { @@ -88,7 +104,7 @@ Switch { Layout.fillWidth: true - color: "#878B91" + color: root.enabled ? root.descriptionTextColor : root.descriptionTextDisabledColor visible: text !== "" } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 76b9f94b..6a134e9b 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -22,22 +22,14 @@ PageType { property string borderColor: "#2C2D30" - property string currentServerName - property string currentServerHostName - property string currentContainerName + property string defaultServerName: ServersModel.defaultServerName + property string defaultServerHostName: ServersModel.defaultServerHostName + property string defaultContainerName: ContainersModel.defaultContainerName ConnectButton { anchors.centerIn: parent } - Connections { - target: ContainersModel - - function onDefaultContainerChanged() { - root.currentContainerName = ContainersModel.getDefaultContainerName() - } - } - Connections { target: PageController @@ -79,7 +71,7 @@ PageType { Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Header1TextType { - text: root.currentServerName + text: root.defaultServerName } Image { @@ -107,7 +99,7 @@ PageType { } } - description += root.currentContainerName + " | " + root.currentServerHostName + description += root.defaultContainerName + " | " + root.defaultServerHostName return description } } @@ -139,14 +131,14 @@ PageType { Layout.topMargin: 24 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - text: root.currentServerName + text: root.defaultServerName } LabelTextType { Layout.bottomMargin: 24 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - text: root.currentServerHostName + text: root.defaultServerHostName } RowLayout { @@ -161,7 +153,7 @@ PageType { rootButtonImageColor: "#0E0E11" rootButtonBackgroundColor: "#D7D8DB" - text: root.currentContainerName + text: root.defaultContainerName textColor: "#0E0E11" headerText: qsTr("Connection protocol") headerBackButtonImage: "qrc:/images/controls/arrow-left.svg" @@ -299,9 +291,6 @@ PageType { ServersModel.currentlyProcessedIndex = index ServersModel.defaultIndex = index - - root.currentServerName = name - root.currentServerHostName = hostName } MouseArea { @@ -331,13 +320,6 @@ PageType { Layout.fillWidth: true } } - - Component.onCompleted: { - if (serversMenuContent.currentIndex === index) { - root.currentServerName = name - root.currentServerHostName = hostName - } - } } } } diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index 028f9fd3..65fcdc4b 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -425,16 +425,13 @@ PageType { onClicked: { questionDrawer.headerText = qsTr("Remove OpenVpn from server?") -// questionDrawer.descriptionText = qsTr("") questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { questionDrawer.visible = false goToPage(PageEnum.PageDeinstalling) - ContainersModel.removeCurrentlyProcessedContainer() - closePage() - closePage() //todo auto close to deinstall page? + InstallController.removeCurrentlyProcessedContainer() } questionDrawer.noButtonFunction = function() { questionDrawer.visible = false diff --git a/client/ui/qml/Pages2/PageProtocolRaw.qml b/client/ui/qml/Pages2/PageProtocolRaw.qml index 377d948e..e82b0ff5 100644 --- a/client/ui/qml/Pages2/PageProtocolRaw.qml +++ b/client/ui/qml/Pages2/PageProtocolRaw.qml @@ -173,8 +173,19 @@ PageType { textColor: "#EB5757" clickedFunction: function() { - ContainersModel.removeCurrentlyProcessedContainer() - closePage() + questionDrawer.headerText = qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() + qsTr(" from server?") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + goToPage(PageEnum.PageDeinstalling) + InstallController.removeCurrentlyProcessedContainer() + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true } MouseArea { @@ -186,5 +197,9 @@ PageType { DividerType {} } + + QuestionDrawer { + id: questionDrawer + } } } diff --git a/client/ui/qml/Pages2/PageServiceSftpSettings.qml b/client/ui/qml/Pages2/PageServiceSftpSettings.qml index ffa42859..0eabb076 100644 --- a/client/ui/qml/Pages2/PageServiceSftpSettings.qml +++ b/client/ui/qml/Pages2/PageServiceSftpSettings.qml @@ -258,9 +258,7 @@ PageType { questionDrawer.yesButtonFunction = function() { questionDrawer.visible = false goToPage(PageEnum.PageDeinstalling) - ContainersModel.removeCurrentlyProcessedContainer() - closePage() - closePage() //todo auto close to deinstall page? + InstallController.removeCurrentlyProcessedContainer() } questionDrawer.noButtonFunction = function() { questionDrawer.visible = false diff --git a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml index a47a95e3..7cf2a81a 100644 --- a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml +++ b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml @@ -150,9 +150,7 @@ PageType { questionDrawer.yesButtonFunction = function() { questionDrawer.visible = false goToPage(PageEnum.PageDeinstalling) - ContainersModel.removeCurrentlyProcessedContainer() - closePage() - closePage() //todo auto close to deinstall page? + InstallController.removeCurrentlyProcessedContainer() } questionDrawer.noButtonFunction = function() { questionDrawer.visible = false diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index 54d315b0..2c7d8601 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -92,6 +92,7 @@ PageType { questionDrawer.yesButtonFunction = function() { questionDrawer.visible = false SettingsController.clearSettings() + PageController.replaceStartPage() } questionDrawer.noButtonFunction = function() { questionDrawer.visible = false diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml index 1c196aa3..cd0b3ee8 100644 --- a/client/ui/qml/Pages2/PageSettingsBackup.qml +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -12,6 +12,20 @@ import "../Controls2/TextTypes" PageType { id: root + Connections { + target: SettingsController + + function onChangeSettingsErrorOccurred(errorMessage) { + PageController.showErrorMessage(errorMessage) + } + + function onRestoreBackupFinished() { + PageController.showNotificationMessage(qsTr("Settings restored from backup file")) + goToStartPage() + PageController.goToPageHome() + } + } + BackButtonType { id: backButton @@ -66,7 +80,9 @@ PageType { text: qsTr("Make a backup") onClicked: { + PageController.showBusyIndicator(true) SettingsController.backupAppConfig() + PageController.showBusyIndicator(false) } } @@ -84,7 +100,9 @@ PageType { text: qsTr("Restore from backup") onClicked: { + PageController.showBusyIndicator(true) SettingsController.restoreAppConfig() + PageController.showBusyIndicator(false) } } } diff --git a/client/ui/qml/Pages2/PageSettingsDns.qml b/client/ui/qml/Pages2/PageSettingsDns.qml index c51f9092..2851e9b1 100644 --- a/client/ui/qml/Pages2/PageSettingsDns.qml +++ b/client/ui/qml/Pages2/PageSettingsDns.qml @@ -55,6 +55,9 @@ PageType { headerText: "Primary DNS" textFieldText: SettingsController.primaryDns + textField.validator: RegularExpressionValidator { + regularExpression: InstallController.ipAddressRegExp() + } } TextFieldWithHeaderType { @@ -64,6 +67,9 @@ PageType { headerText: "Secondary DNS" textFieldText: SettingsController.secondaryDns + textField.validator: RegularExpressionValidator { + regularExpression: InstallController.ipAddressRegExp() + } } BasicButtonType { diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 20afecd2..b24411c7 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -27,6 +27,32 @@ PageType { PageController.showErrorMessage(message) } + + function onInstallationErrorOccurred(errorMessage) { + closePage() // close deInstalling page + PageController.showErrorMessage(errorMessage) + } + + function onRemoveCurrentlyProcessedServerFinished(finishedMessage) { + if (!ServersModel.getServersCount()) { + PageController.replaceStartPage() + } else { + goToStartPage() + goToPage(PageEnum.PageSettingsServersList) + } + PageController.showNotificationMessage(finishedMessage) + } + + function onRemoveAllContainersFinished(finishedMessage) { + closePage() // close deInstalling page + PageController.showNotificationMessage(finishedMessage) + } + + function onRemoveCurrentlyProcessedContainerFinished(finishedMessage) { + closePage() // close deInstalling page + closePage() // close page with remove button + PageController.showNotificationMessage(finishedMessage) + } } Connections { @@ -112,16 +138,12 @@ PageType { questionDrawer.yesButtonFunction = function() { questionDrawer.visible = false + PageController.showBusyIndicator(true) if (ServersModel.isDefaultServerCurrentlyProcessed && ConnectionController.isConnected) { ConnectionController.closeConnection() } - ServersModel.removeServer() - if (!ServersModel.getServersCount()) { - PageController.replaceStartPage() - } else { - goToStartPage() - goToPage(PageEnum.PageSettingsServersList) - } + InstallController.removeCurrentlyProcessedServer() + PageController.showBusyIndicator(false) } questionDrawer.noButtonFunction = function() { questionDrawer.visible = false @@ -151,8 +173,7 @@ PageType { if (ServersModel.isDefaultServerCurrentlyProcessed && ConnectionController.isConnected) { ConnectionController.closeVpnConnection() } - ContainersModel.removeAllContainers() - closePage() + InstallController.removeAllContainers() } questionDrawer.noButtonFunction = function() { questionDrawer.visible = false diff --git a/client/ui/qml/Pages2/PageSettingsServerInfo.qml b/client/ui/qml/Pages2/PageSettingsServerInfo.qml index 7b6dce68..cf748f54 100644 --- a/client/ui/qml/Pages2/PageSettingsServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsServerInfo.qml @@ -93,6 +93,7 @@ PageType { Layout.fillWidth: true headerText: qsTr("Server name") textFieldText: name + textField.maximumLength: 20 } BasicButtonType { diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml index ec2ca91f..e417eea9 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -81,13 +81,12 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { - var containerIndex = ContainersModel.getCurrentlyProcessedContainerIndex() - switch (containerIndex) { - case ContainerEnum.OpenVpn: OpenVpnConfigModel.updateModel(ProtocolsModel.getConfig()); break; - case ContainerEnum.ShadowSocks: ShadowSocksConfigModel.updateModel(ProtocolsModel.getConfig()); break; - case ContainerEnum.Cloak: CloakConfigModel.updateModel(ProtocolsModel.getConfig()); break; - case ContainerEnum.WireGuard: WireGuardConfigModel.updateModel(ProtocolsModel.getConfig()); break; - case ContainerEnum.Ipsec: Ikev2ConfigModel.updateModel(ProtocolsModel.getConfig()); break; + switch (protocolIndex) { + case ProtocolEnum.OpenVpn: OpenVpnConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ProtocolEnum.ShadowSocks: ShadowSocksConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ProtocolEnum.Cloak: CloakConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ProtocolEnum.WireGuard: WireGuardConfigModel.updateModel(ProtocolsModel.getConfig()); break; + case ProtocolEnum.Ipsec: Ikev2ConfigModel.updateModel(ProtocolsModel.getConfig()); break; } goToPage(protocolPage); } @@ -113,8 +112,19 @@ PageType { textColor: "#EB5757" clickedFunction: function() { - ContainersModel.removeCurrentlyProcessedContainer() - closePage() + questionDrawer.headerText = qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() + qsTr(" from server?") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + goToPage(PageEnum.PageDeinstalling) + InstallController.removeCurrentlyProcessedContainer() + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true } MouseArea { @@ -126,5 +136,9 @@ PageType { DividerType {} } + + QuestionDrawer { + id: questionDrawer + } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index 92c95108..f254a474 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -22,7 +22,7 @@ PageType { PageController.showErrorMessage(errorMessage) } - function onInstallContainerFinished(isInstalledContainerFound) { + function onInstallContainerFinished(finishedMessage) { goToStartPage() if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) { PageController.restorePageHomeState(true) @@ -33,14 +33,10 @@ PageType { goToPage(PageEnum.PageHome) } - if (isInstalledContainerFound) { - //todo change to info message - PageController.showErrorMessage(qsTr("The container you are trying to install is already installed on the server. " + - "All installed containers have been added to the application")) - } + PageController.showNotificationMessage(finishedMessage) } - function onInstallServerFinished(isInstalledContainerFound) { + function onInstallServerFinished(finishedMessage) { goToStartPage() if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) { PageController.restorePageHomeState() @@ -50,10 +46,7 @@ PageType { PageController.replaceStartPage() } - if (isInstalledContainerFound) { - PageController.showErrorMessage(qsTr("The container you are trying to install is already installed on the server. " + - "All installed containers have been added to the application")) - } + PageController.showNotificationMessage(finishedMessage) } function onServerAlreadyExists(serverIndex) { diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 29067cb2..65e00da8 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -123,15 +123,16 @@ PageType { anchors.bottom: parent.bottom contentHeight: { var emptySpaceHeight = parent.height - showDetailsBackButton.implicitHeight - showDetailsBackButton.anchors.topMargin - - return (showDetailsDrawerContent.implicitHeight > emptySpaceHeight) ? - showDetailsDrawerContent.implicitHeight : emptySpaceHeight + return (showDetailsDrawerContent.height > emptySpaceHeight) ? + showDetailsDrawerContent.height : emptySpaceHeight } ColumnLayout { id: showDetailsDrawerContent - anchors.fill: parent + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right anchors.rightMargin: 16 anchors.leftMargin: 16 diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index 73438e34..a2f42cc2 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -16,11 +16,6 @@ PageType { Connections { target: PageController - function onShowErrorMessage(errorMessage) { - popupErrorMessage.popupErrorMessageText = errorMessage - popupErrorMessage.open() - } - function onGoToPageViewConfig() { goToPage(PageEnum.PageSetupWizardViewConfig) } @@ -98,16 +93,4 @@ PageType { id: connectionTypeSelection } } - - Item { - anchors.right: parent.right - anchors.left: parent.left - anchors.bottom: parent.bottom - - implicitHeight: popupErrorMessage.height - - PopupType { - id: popupErrorMessage - } - } } diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 201c630a..3d5851b1 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -58,7 +58,7 @@ PageType { property string fullConfigServerSelectorText property string connectionServerSelectorText property bool showContent: false - property bool shareButtonEnabled: false + property bool shareButtonEnabled: true property list connectionTypesModel: [ amneziaConnectionFormat ] @@ -140,6 +140,7 @@ PageType { onClicked: { accessTypeSelector.currentIndex = 1 serverSelector.text = root.fullConfigServerSelectorText + root.shareButtonEnabled = true } } } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index f522bace..811fc923 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -31,11 +31,6 @@ PageType { tabBarStackView.push(pagePath, { "objectName" : pagePath }, StackView.PushTransition) } - function onShowErrorMessage(errorMessage) { - popupErrorMessage.popupErrorMessageText = errorMessage - popupErrorMessage.open() - } - function onShowBusyIndicator(visible) { busyIndicator.visible = visible tabBarStackView.enabled = !visible @@ -119,14 +114,15 @@ PageType { Connections { target: ServersModel - function onDefaultServerIndexChanged() { - shareTabButton.visible = ServersModel.isCurrentlyProcessedServerHasWriteAccess() - shareTabButton.width = ServersModel.isCurrentlyProcessedServerHasWriteAccess() ? undefined : 0 + function onModelReset() { + var hasServerWithWriteAccess = ServersModel.hasServerWithWriteAccess() + shareTabButton.visible = hasServerWithWriteAccess + shareTabButton.width = hasServerWithWriteAccess ? undefined : 0 } } - visible: ServersModel.isCurrentlyProcessedServerHasWriteAccess() - width: ServersModel.isCurrentlyProcessedServerHasWriteAccess() ? undefined : 0 + visible: ServersModel.hasServerWithWriteAccess() + width: ServersModel.hasServerWithWriteAccess() ? undefined : 0 isSelected: tabBar.currentIndex === 1 image: "qrc:/images/controls/share-2.svg" @@ -151,18 +147,6 @@ PageType { enabled: false } - Item { - anchors.right: parent.right - anchors.left: parent.left - anchors.bottom: parent.bottom - - implicitHeight: popupErrorMessage.height - - PopupType { - id: popupErrorMessage - } - } - BusyIndicatorType { id: busyIndicator anchors.centerIn: parent diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index 0b89d840..6b2bee2a 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -63,5 +63,52 @@ Window { function onHideMainWindow() { root.hide() } + + function onShowErrorMessage(errorMessage) { + popupErrorMessage.text = errorMessage + popupErrorMessage.open() + } + + function onShowNotificationMessage(message) { + popupNotificationMessage.text = message + popupNotificationMessage.closeButtonVisible = false + popupNotificationMessage.open() + popupNotificationTimer.start() + } + } + + Item { + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + + implicitHeight: popupNotificationMessage.height + + PopupType { + id: popupNotificationMessage + } + + Timer { + id: popupNotificationTimer + + interval: 3000 + repeat: false + running: false + onTriggered: { + popupNotificationMessage.close() + } + } + } + + Item { + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + + implicitHeight: popupErrorMessage.height + + PopupType { + id: popupErrorMessage + } } } From 66f9a82f318de52159ef495dd1a3f87568f5500e Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 31 Jul 2023 12:54:59 +0900 Subject: [PATCH 050/278] added icons for buttons in the drop-down window of connections sharing. - corrections in texts --- client/containers/containers_defs.cpp | 180 +++++++++--------- .../qml/Components/ShareConnectionDrawer.qml | 2 + client/ui/qml/Controls2/BasicButtonType.qml | 33 +++- client/ui/qml/Pages2/PageHome.qml | 2 +- .../qml/Pages2/PageProtocolCloakSettings.qml | 4 +- .../Pages2/PageProtocolOpenVpnSettings.qml | 2 +- .../PageProtocolShadowSocksSettings.qml | 2 +- .../ui/qml/Pages2/PageSettingsConnection.qml | 2 +- .../ui/qml/Pages2/PageSettingsServerData.qml | 2 +- .../qml/Pages2/PageSetupWizardCredentials.qml | 5 +- .../qml/Pages2/PageSetupWizardProtocols.qml | 2 +- client/ui/qml/Pages2/PageSetupWizardStart.qml | 3 +- client/ui/qml/Pages2/PageShare.qml | 6 +- 13 files changed, 136 insertions(+), 109 deletions(-) diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index 8b979296..1e8046bf 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -8,18 +8,23 @@ QDebug operator<<(QDebug debug, const amnezia::DockerContainer &c) return debug; } -amnezia::DockerContainer ContainerProps::containerFromString(const QString &container){ +amnezia::DockerContainer ContainerProps::containerFromString(const QString &container) +{ QMetaEnum metaEnum = QMetaEnum::fromType(); for (int i = 0; i < metaEnum.keyCount(); ++i) { DockerContainer c = static_cast(i); - if (container == containerToString(c)) return c; + if (container == containerToString(c)) + return c; } return DockerContainer::None; } -QString ContainerProps::containerToString(amnezia::DockerContainer c){ - if (c == DockerContainer::None) return "none"; - if (c == DockerContainer::Cloak) return "amnezia-openvpn-cloak"; +QString ContainerProps::containerToString(amnezia::DockerContainer c) +{ + if (c == DockerContainer::None) + return "none"; + if (c == DockerContainer::Cloak) + return "amnezia-openvpn-cloak"; QMetaEnum metaEnum = QMetaEnum::fromType(); QString containerKey = metaEnum.valueToKey(static_cast(c)); @@ -27,9 +32,12 @@ QString ContainerProps::containerToString(amnezia::DockerContainer c){ return "amnezia-" + containerKey.toLower(); } -QString ContainerProps::containerTypeToString(amnezia::DockerContainer c){ - if (c == DockerContainer::None) return "none"; - if (c == DockerContainer::Ipsec) return "ikev2"; +QString ContainerProps::containerTypeToString(amnezia::DockerContainer c) +{ + if (c == DockerContainer::None) + return "none"; + if (c == DockerContainer::Ipsec) + return "ikev2"; QMetaEnum metaEnum = QMetaEnum::fromType(); QString containerKey = metaEnum.valueToKey(static_cast(c)); @@ -40,29 +48,21 @@ QString ContainerProps::containerTypeToString(amnezia::DockerContainer c){ QVector ContainerProps::protocolsForContainer(amnezia::DockerContainer container) { switch (container) { - case DockerContainer::None: - return { }; + case DockerContainer::None: return {}; - case DockerContainer::OpenVpn: - return { Proto::OpenVpn }; + case DockerContainer::OpenVpn: return { Proto::OpenVpn }; - case DockerContainer::ShadowSocks: - return { Proto::OpenVpn, Proto::ShadowSocks }; + case DockerContainer::ShadowSocks: return { Proto::OpenVpn, Proto::ShadowSocks }; - case DockerContainer::Cloak: - return { Proto::OpenVpn, Proto::ShadowSocks, Proto::Cloak }; + case DockerContainer::Cloak: return { Proto::OpenVpn, Proto::ShadowSocks, Proto::Cloak }; - case DockerContainer::Ipsec: - return { Proto::Ikev2 /*, Protocol::L2tp */}; + case DockerContainer::Ipsec: return { Proto::Ikev2 /*, Protocol::L2tp */ }; - case DockerContainer::Dns: - return { }; + case DockerContainer::Dns: return {}; - case DockerContainer::Sftp: - return { Proto::Sftp}; + case DockerContainer::Sftp: return { Proto::Sftp }; - default: - return { defaultProtocol(container) }; + default: return { defaultProtocol(container) }; } } @@ -79,70 +79,67 @@ QList ContainerProps::allContainers() QMap ContainerProps::containerHumanNames() { - return { - {DockerContainer::None, "Not installed"}, - {DockerContainer::OpenVpn, "OpenVPN"}, - {DockerContainer::ShadowSocks, "OpenVpn over ShadowSocks"}, - {DockerContainer::Cloak, "OpenVpn over Cloak"}, - {DockerContainer::WireGuard, "WireGuard"}, - {DockerContainer::Ipsec, QObject::tr("IPsec")}, + return { { DockerContainer::None, "Not installed" }, + { DockerContainer::OpenVpn, "OpenVPN" }, + { DockerContainer::ShadowSocks, "OpenVPN over ShadowSocks" }, + { DockerContainer::Cloak, "OpenVPN over Cloak" }, + { DockerContainer::WireGuard, "WireGuard" }, + { DockerContainer::Ipsec, QObject::tr("IPsec") }, - {DockerContainer::TorWebSite, QObject::tr("Web site in Tor network")}, - {DockerContainer::Dns, QObject::tr("DNS Service")}, - //{DockerContainer::FileShare, QObject::tr("SMB file sharing service")}, - {DockerContainer::Sftp, QObject::tr("Sftp file sharing service")} - }; + { DockerContainer::TorWebSite, QObject::tr("Web site in Tor network") }, + { DockerContainer::Dns, QObject::tr("Amnezia DNS") }, + //{DockerContainer::FileShare, QObject::tr("SMB file sharing service")}, + { DockerContainer::Sftp, QObject::tr("Sftp file sharing service") } }; } QMap ContainerProps::containerDescriptions() { - return { - {DockerContainer::OpenVpn, QObject::tr("OpenVPN container")}, - {DockerContainer::ShadowSocks, QObject::tr("Container with OpenVpn and ShadowSocks")}, - {DockerContainer::Cloak, QObject::tr("Container with OpenVpn and ShadowSocks protocols " - "configured with traffic masking by Cloak plugin")}, - {DockerContainer::WireGuard, QObject::tr("WireGuard container")}, - {DockerContainer::Ipsec, QObject::tr("IPsec container")}, + return { { DockerContainer::OpenVpn, QObject::tr("OpenVPN container") }, + { DockerContainer::ShadowSocks, QObject::tr("Container with OpenVpn and ShadowSocks") }, + { DockerContainer::Cloak, + QObject::tr("Container with OpenVpn and ShadowSocks protocols " + "configured with traffic masking by Cloak plugin") }, + { DockerContainer::WireGuard, QObject::tr("WireGuard container") }, + { DockerContainer::Ipsec, QObject::tr("IPsec container") }, - {DockerContainer::TorWebSite, QObject::tr("Web site in Tor network")}, - {DockerContainer::Dns, QObject::tr("DNS Service")}, - //{DockerContainer::FileShare, QObject::tr("SMB file sharing service - is Window file sharing protocol")}, - {DockerContainer::Sftp, QObject::tr("Sftp file sharing service - is secure FTP service")} - }; + { DockerContainer::TorWebSite, QObject::tr("Web site in Tor network") }, + { DockerContainer::Dns, QObject::tr("DNS Service") }, + //{DockerContainer::FileShare, QObject::tr("SMB file sharing service - is Window file sharing protocol")}, + { DockerContainer::Sftp, QObject::tr("Sftp file sharing service - is secure FTP service") } }; } amnezia::ServiceType ContainerProps::containerService(DockerContainer c) { switch (c) { - case DockerContainer::None : return ServiceType::None; - case DockerContainer::OpenVpn : return ServiceType::Vpn; - case DockerContainer::Cloak : return ServiceType::Vpn; - case DockerContainer::ShadowSocks : return ServiceType::Vpn; - case DockerContainer::WireGuard : return ServiceType::Vpn; - case DockerContainer::Ipsec : return ServiceType::Vpn; - case DockerContainer::TorWebSite : return ServiceType::Other; - case DockerContainer::Dns : return ServiceType::Other; - //case DockerContainer::FileShare : return ServiceType::Other; - case DockerContainer::Sftp : return ServiceType::Other; - default: return ServiceType::Other; + case DockerContainer::None: return ServiceType::None; + case DockerContainer::OpenVpn: return ServiceType::Vpn; + case DockerContainer::Cloak: return ServiceType::Vpn; + case DockerContainer::ShadowSocks: return ServiceType::Vpn; + case DockerContainer::WireGuard: return ServiceType::Vpn; + case DockerContainer::Ipsec: return ServiceType::Vpn; + case DockerContainer::TorWebSite: return ServiceType::Other; + case DockerContainer::Dns: return ServiceType::Other; + // case DockerContainer::FileShare : return ServiceType::Other; + case DockerContainer::Sftp: return ServiceType::Other; + default: return ServiceType::Other; } } Proto ContainerProps::defaultProtocol(DockerContainer c) { switch (c) { - case DockerContainer::None : return Proto::Any; - case DockerContainer::OpenVpn : return Proto::OpenVpn; - case DockerContainer::Cloak : return Proto::Cloak; - case DockerContainer::ShadowSocks : return Proto::ShadowSocks; - case DockerContainer::WireGuard : return Proto::WireGuard; - case DockerContainer::Ipsec : return Proto::Ikev2; + case DockerContainer::None: return Proto::Any; + case DockerContainer::OpenVpn: return Proto::OpenVpn; + case DockerContainer::Cloak: return Proto::Cloak; + case DockerContainer::ShadowSocks: return Proto::ShadowSocks; + case DockerContainer::WireGuard: return Proto::WireGuard; + case DockerContainer::Ipsec: return Proto::Ikev2; - case DockerContainer::TorWebSite : return Proto::TorWebSite; - case DockerContainer::Dns : return Proto::Dns; - //case DockerContainer::FileShare : return Protocol::FileShare; - case DockerContainer::Sftp : return Proto::Sftp; - default: return Proto::Any; + case DockerContainer::TorWebSite: return Proto::TorWebSite; + case DockerContainer::Dns: return Proto::Dns; + // case DockerContainer::FileShare : return Protocol::FileShare; + case DockerContainer::Sftp: return Proto::Sftp; + default: return Proto::Any; } } @@ -151,22 +148,23 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c) #ifdef Q_OS_WINDOWS return true; -#elif defined (Q_OS_IOS) +#elif defined(Q_OS_IOS) switch (c) { case DockerContainer::WireGuard: return true; case DockerContainer::OpenVpn: return true; - case DockerContainer::Cloak: return true; -// case DockerContainer::ShadowSocks: return true; + case DockerContainer::Cloak: + return true; + // case DockerContainer::ShadowSocks: return true; default: return false; } -#elif defined (Q_OS_MAC) +#elif defined(Q_OS_MAC) switch (c) { case DockerContainer::WireGuard: return true; case DockerContainer::Ipsec: return false; default: return true; } -#elif defined (Q_OS_ANDROID) +#elif defined(Q_OS_ANDROID) switch (c) { case DockerContainer::WireGuard: return true; case DockerContainer::OpenVpn: return true; @@ -175,7 +173,7 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c) default: return false; } -#elif defined (Q_OS_LINUX) +#elif defined(Q_OS_LINUX) switch (c) { case DockerContainer::WireGuard: return true; case DockerContainer::Ipsec: return false; @@ -183,44 +181,44 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c) } #else -return false; + return false; #endif } QStringList ContainerProps::fixedPortsForContainer(DockerContainer c) { switch (c) { - case DockerContainer::Ipsec : return QStringList{"500", "4500"}; - default: return {}; + case DockerContainer::Ipsec: return QStringList { "500", "4500" }; + default: return {}; } } bool ContainerProps::isEasySetupContainer(DockerContainer container) { switch (container) { - case DockerContainer::OpenVpn : return true; - case DockerContainer::Cloak : return true; - case DockerContainer::ShadowSocks : return true; - default: return false; + case DockerContainer::OpenVpn: return true; + case DockerContainer::Cloak: return true; + case DockerContainer::ShadowSocks: return true; + default: return false; } } QString ContainerProps::easySetupHeader(DockerContainer container) { switch (container) { - case DockerContainer::OpenVpn : return tr("Low"); - case DockerContainer::Cloak : return tr("High"); - case DockerContainer::ShadowSocks : return tr("Medium"); - default: return ""; + case DockerContainer::OpenVpn: return tr("Low"); + case DockerContainer::Cloak: return tr("High"); + case DockerContainer::ShadowSocks: return tr("Medium"); + default: return ""; } } QString ContainerProps::easySetupDescription(DockerContainer container) { switch (container) { - case DockerContainer::OpenVpn : return tr("Many foreign websites and VPN providers are blocked"); - case DockerContainer::Cloak : return tr("Some foreign sites are blocked, but VPN providers are not blocked"); - case DockerContainer::ShadowSocks : return tr("I just want to increase the level of privacy"); - default: return ""; + case DockerContainer::OpenVpn: return tr("I just want to increase the level of privacy"); + case DockerContainer::Cloak: return tr("Some foreign sites are blocked, but VPN providers are not blocked"); + case DockerContainer::ShadowSocks: return tr("Many foreign websites and VPN providers are blocked"); + default: return ""; } } diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index f133f27a..3f27396b 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -55,6 +55,7 @@ DrawerType { Layout.topMargin: 16 text: qsTr("Share") + imageSource: "qrc:/images/controls/share-2.svg" onClicked: { ExportController.saveFile() @@ -73,6 +74,7 @@ DrawerType { borderWidth: 1 text: qsTr("Copy") + imageSource: "qrc:/images/controls/copy.svg" onClicked: { configText.selectAll() diff --git a/client/ui/qml/Controls2/BasicButtonType.qml b/client/ui/qml/Controls2/BasicButtonType.qml index d9168466..aa05774e 100644 --- a/client/ui/qml/Controls2/BasicButtonType.qml +++ b/client/ui/qml/Controls2/BasicButtonType.qml @@ -1,5 +1,7 @@ import QtQuick import QtQuick.Controls +import QtQuick.Layouts +import Qt5Compat.GraphicalEffects import "TextTypes" @@ -16,6 +18,8 @@ Button { property string borderColor: "#D7D8DB" property int borderWidth: 0 + property string imageSource + implicitHeight: 56 hoverEnabled: true @@ -48,11 +52,30 @@ Button { cursorShape: Qt.PointingHandCursor } - contentItem: ButtonTextType { + contentItem: Item { anchors.fill: background - color: textColor - text: root.text - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter + RowLayout { + anchors.centerIn: parent + + Image { + source: root.imageSource + visible: root.imageSource === "" ? false : true + + layer { + enabled: true + effect: ColorOverlay { + color: textColor + } + } + } + + ButtonTextType { + color: textColor + text: root.text + + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + } + } } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 6a134e9b..ce5ddcd4 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -155,7 +155,7 @@ PageType { text: root.defaultContainerName textColor: "#0E0E11" - headerText: qsTr("Connection protocol") + headerText: qsTr("VPN protocol") headerBackButtonImage: "qrc:/images/controls/arrow-left.svg" rootButtonClickedFunction: function() { diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml index 33d231b5..54c2ffb8 100644 --- a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -92,7 +92,7 @@ PageType { Layout.fillWidth: true Layout.topMargin: 32 - headerText: qsTr("Masquerading as traffic from") + headerText: qsTr("Disguised as traffic from") textFieldText: site textField.onEditingFinished: { @@ -161,7 +161,7 @@ PageType { Layout.topMargin: 24 Layout.bottomMargin: 24 - text: qsTr("Save and Restart Amnesia") + text: qsTr("Save and Restart Amnezia") onClicked: { forceActiveFocus() diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index 65fcdc4b..26a9c495 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -445,7 +445,7 @@ PageType { Layout.topMargin: 24 Layout.bottomMargin: 24 - text: qsTr("Save and Restart Amnesia") + text: qsTr("Save and Restart Amnezia") onClicked: { forceActiveFocus() diff --git a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml index 730e3907..d873beac 100644 --- a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml @@ -147,7 +147,7 @@ PageType { Layout.topMargin: 24 Layout.bottomMargin: 24 - text: qsTr("Save and Restart Amnesia") + text: qsTr("Save and Restart Amnezia") onClicked: { forceActiveFocus() diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml index 1cf8a1a5..fd365edb 100644 --- a/client/ui/qml/Pages2/PageSettingsConnection.qml +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -48,7 +48,7 @@ PageType { Layout.leftMargin: 16 Layout.rightMargin: 16 - text: qsTr("Use AmnesiaDNS if installed on the server") + text: qsTr("Use AmneziaDNS if installed on the server") descriptionText: qsTr("Internal IP address 172.29.172.254") checked: SettingsController.isAmneziaDnsEnabled() diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index b24411c7..3aec4242 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -110,7 +110,7 @@ PageType { visible: content.isServerWithWriteAccess Layout.fillWidth: true - text: qsTr("Check the server for previously installed Amnesia services") + text: qsTr("Check the server for previously installed Amnezia services") descriptionText: qsTr("Add them to the application if they were not displayed") clickedFunction: function() { diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index 487bdbde..b72fe988 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -49,7 +49,7 @@ PageType { Layout.fillWidth: true headerText: qsTr("Server IP address [:port]") - textFieldPlaceholderText: qsTr("Enter the address in the format 255.255.255.255:88") + textFieldPlaceholderText: qsTr("255.255.255.255:88") textField.validator: RegularExpressionValidator { regularExpression: InstallController.ipAddressPortRegExp() } @@ -60,13 +60,14 @@ PageType { Layout.fillWidth: true headerText: qsTr("Login to connect via SSH") + textFieldPlaceholderText: "root" } TextFieldWithHeaderType { id: secretData Layout.fillWidth: true - headerText: qsTr("Password / Private key") + headerText: qsTr("Password / SSH private key") textField.echoMode: TextInput.Password } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index f3de85b6..8c7f5de0 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -70,7 +70,7 @@ PageType { width: parent.width - headerText: qsTr("Connection protocol") + headerText: qsTr("VPN protocol") descriptionText: qsTr("Choose the one with the highest priority for you. Later, you can install other protocols and additional services, such as DNS proxy and SFTP.") } } diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index a2f42cc2..eff17ab5 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -53,7 +53,8 @@ PageType { Layout.leftMargin: 16 Layout.rightMargin: 16 - text: qsTr("A free service to create a personal VPN on your server. We help you access blocked content without exposing your privacy even to VPN providers.") + text: qsTr("Free service for creating a personal VPN on your server.") + + qsTr(" Helps you access blocked content without revealing your privacy, even to VPN providers.") } BasicButtonType { diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 3d5851b1..ca9dedd4 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -65,7 +65,7 @@ PageType { QtObject { id: amneziaConnectionFormat - property string name: qsTr("For the AmnesiaVPN app") + property string name: qsTr("For the AmneziaVPN app") property var type: PageShare.ConfigType.AmneziaConnection } QtObject { @@ -135,7 +135,7 @@ PageType { checked: root.currentIndex === 1 implicitWidth: (root.width - 32) / 2 - text: qsTr("Full") + text: qsTr("Full access") onClicked: { accessTypeSelector.currentIndex = 1 @@ -194,6 +194,8 @@ PageType { protocolSelector.visible = true root.shareButtonEnabled = false } else { + shareConnectionDrawer.headerText = qsTr("Accessing ") + serverSelector.text + shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text serverSelector.menuVisible = false } } From aa66133813e375c9ec151967a80b04ae6f51bf25 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 31 Jul 2023 14:29:49 +0900 Subject: [PATCH 051/278] added 'insert' button and 'show password' button for PageSetupWizardCredentials --- .gitignore | 1 + CMakeLists.txt | 4 ++-- client/images/controls/eye-off.svg | 6 ++++++ client/images/controls/eye.svg | 4 ++++ client/resources.qrc | 2 ++ client/ui/qml/Config/GlobalConfig.qml | 10 ++++----- client/ui/qml/Controls2/BasicButtonType.qml | 5 +++++ .../qml/Controls2/TextFieldWithHeaderType.qml | 5 ++++- .../Pages2/PageSetupWizardConfigSource.qml | 6 ++++-- .../qml/Pages2/PageSetupWizardCredentials.qml | 21 ++++++++++++++++++- 10 files changed, 53 insertions(+), 11 deletions(-) create mode 100644 client/images/controls/eye-off.svg create mode 100644 client/images/controls/eye.svg diff --git a/.gitignore b/.gitignore index 88a3b397..7de64e4b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ deploy/build/* deploy/build_32/* deploy/build_64/* winbuild*.bat +.cache/ # Qt-es diff --git a/CMakeLists.txt b/CMakeLists.txt index ad9866e0..2ff60079 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,11 +2,11 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 3.0.8.1 +project(${PROJECT} VERSION 4.0.0.1 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) -set(RELEASE_DATE "2023-07-15") +set(RELEASE_DATE "2023-07-31") set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") diff --git a/client/images/controls/eye-off.svg b/client/images/controls/eye-off.svg new file mode 100644 index 00000000..d05e0b85 --- /dev/null +++ b/client/images/controls/eye-off.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/client/images/controls/eye.svg b/client/images/controls/eye.svg new file mode 100644 index 00000000..a01452af --- /dev/null +++ b/client/images/controls/eye.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/resources.qrc b/client/resources.qrc index 85ee838f..625292c2 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -278,5 +278,7 @@ images/controls/copy.svg ui/qml/Pages2/PageServiceTorWebsiteSettings.qml ui/qml/Pages2/PageSetupWizardQrReader.qml + images/controls/eye.svg + images/controls/eye-off.svg diff --git a/client/ui/qml/Config/GlobalConfig.qml b/client/ui/qml/Config/GlobalConfig.qml index 5bb71b6f..a9edd543 100644 --- a/client/ui/qml/Config/GlobalConfig.qml +++ b/client/ui/qml/Config/GlobalConfig.qml @@ -11,17 +11,17 @@ Item { readonly property int defaultMargin: 20 function isMobile() { - if (Qt.platform.os == "android" || - Qt.platform.os == "ios") { + if (Qt.platform.os === "android" || + Qt.platform.os === "ios") { return true } return false } function isDesktop() { - if (Qt.platform.os == "windows" || - Qt.platform.os == "linux" || - Qt.platform.os == "osx") { + if (Qt.platform.os === "windows" || + Qt.platform.os === "linux" || + Qt.platform.os === "osx") { return true } return false diff --git a/client/ui/qml/Controls2/BasicButtonType.qml b/client/ui/qml/Controls2/BasicButtonType.qml index aa05774e..c69d51d7 100644 --- a/client/ui/qml/Controls2/BasicButtonType.qml +++ b/client/ui/qml/Controls2/BasicButtonType.qml @@ -54,7 +54,11 @@ Button { contentItem: Item { anchors.fill: background + + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight RowLayout { + id: content anchors.centerIn: parent Image { @@ -72,6 +76,7 @@ Button { ButtonTextType { color: textColor text: root.text + visible: root.text === "" ? false : true horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml index 13a7ea6e..1414b5ec 100644 --- a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -14,6 +14,7 @@ Item { property alias errorText: errorField.text property string buttonText + property string buttonImageSource property var clickedFunc property alias textField: textField @@ -101,7 +102,7 @@ Item { } BasicButtonType { - visible: root.buttonText !== "" + visible: (root.buttonText !== "") || (root.buttonImageSource !== "") defaultColor: "transparent" hoveredColor: Qt.rgba(1, 1, 1, 0.08) @@ -111,8 +112,10 @@ Item { borderWidth: 0 text: root.buttonText + imageSource: root.buttonImageSource Layout.rightMargin: 24 + Layout.preferredHeight: 32 onClicked: { if (root.clickedFunc && typeof root.clickedFunc === "function") { diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 2d6b249d..cd0c08fb 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -76,9 +76,9 @@ It's okay if a friend passed the code.") DividerType {} - //todo ifdef mobile platforms LabelWithButtonType { Layout.fillWidth: true + visible: GC.isMobile() text: qsTr("QR-code") rightImageSource: "qrc:/images/controls/chevron-right.svg" @@ -90,7 +90,9 @@ It's okay if a friend passed the code.") } } - DividerType {} + DividerType { + visible: GC.isMobile() + } LabelWithButtonType { Layout.fillWidth: true diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index b72fe988..cc1197ad 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -53,6 +53,12 @@ PageType { textField.validator: RegularExpressionValidator { regularExpression: InstallController.ipAddressPortRegExp() } + buttonText: qsTr("Insert") + + clickedFunc: function() { + textField.text = "" + textField.paste() + } } TextFieldWithHeaderType { @@ -61,14 +67,27 @@ PageType { Layout.fillWidth: true headerText: qsTr("Login to connect via SSH") textFieldPlaceholderText: "root" + buttonText: qsTr("Insert") + + clickedFunc: function() { + textField.text = "" + textField.paste() + } } TextFieldWithHeaderType { id: secretData + property bool hidePassword: true + Layout.fillWidth: true headerText: qsTr("Password / SSH private key") - textField.echoMode: TextInput.Password + textField.echoMode: hidePassword ? TextInput.Password : TextInput.Normal + buttonImageSource: hidePassword ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg" + + clickedFunc: function() { + hidePassword = !hidePassword + } } BasicButtonType { From 0058edc24ef1c8c84cbf1866a08e704f461e7d3a Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 31 Jul 2023 20:38:13 +0900 Subject: [PATCH 052/278] added server availability check after entering credentials - moved the protocol self-selection button to the PageSetupWizardEasy page --- client/amnezia_application.cpp | 9 + client/amnezia_application.h | 3 + client/translations/amneziavpn_ru.ts | 1271 ++++++++++++----- client/ui/controllers/installController.cpp | 21 + client/ui/controllers/installController.h | 2 + client/ui/models/languageModel.cpp | 2 +- client/ui/models/languageModel.h | 6 +- .../qml/Components/SelectLanguageDrawer.qml | 3 +- client/ui/qml/Controls2/PopupType.qml | 2 + .../qml/Pages2/PageProtocolCloakSettings.qml | 6 +- .../Pages2/PageProtocolOpenVpnSettings.qml | 6 +- .../PageProtocolShadowSocksSettings.qml | 6 +- .../ui/qml/Pages2/PageServiceSftpSettings.qml | 6 +- .../Pages2/PageServiceTorWebsiteSettings.qml | 6 +- .../ui/qml/Pages2/PageSettingsApplication.qml | 2 +- .../ui/qml/Pages2/PageSettingsServerData.qml | 5 - .../qml/Pages2/PageSetupWizardCredentials.qml | 47 +- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 34 +- .../qml/Pages2/PageSetupWizardInstalling.qml | 5 - client/ui/qml/Pages2/PageSetupWizardStart.qml | 3 +- client/ui/qml/Pages2/PageStart.qml | 20 + 21 files changed, 1002 insertions(+), 463 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 2ee9af16..7d7a0376 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -221,6 +221,12 @@ void AmneziaApplication::updateTranslator(const QLocale &locale) QResource::registerResource(":/translations.qrc"); if (!m_translator->isEmpty()) QCoreApplication::removeTranslator(m_translator); + + if (locale == QLocale::English) { + m_settings->setAppLanguage(locale); + m_engine->retranslate(); + } + if (m_translator->load(locale, QString("amneziavpn"), QLatin1String("_"), QLatin1String(":/i18n"))) { if (QCoreApplication::installTranslator(m_translator)) { m_settings->setAppLanguage(locale); @@ -228,6 +234,8 @@ void AmneziaApplication::updateTranslator(const QLocale &locale) m_engine->retranslate(); } + + emit translationsUpdated(); } bool AmneziaApplication::parseCommands() @@ -271,6 +279,7 @@ void AmneziaApplication::initModels() m_languageModel.reset(new LanguageModel(m_settings, this)); m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get()); connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &AmneziaApplication::updateTranslator); + connect(this, &AmneziaApplication::translationsUpdated, m_languageModel.get(), &LanguageModel::translationsUpdated); m_protocolsModel.reset(new ProtocolsModel(m_settings, this)); m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get()); diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 3ba42e41..2a10aa86 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -65,6 +65,9 @@ public: QQmlApplicationEngine *qmlEngine() const; +signals: + void translationsUpdated(); + private: void initModels(); void initControllers(); diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index 197c9d40..f338216b 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -138,6 +138,24 @@ + + ConnectionTypeSelectionDrawer + + + Connection data + + + + + Server IP, login and password + + + + + QR code, key or configuration file + + + ContextMenu @@ -164,11 +182,71 @@ ExportController - + + Access error! + + + + Save AmneziaVPN config + + ImportController + + + Open config file + + + + + InstallController + + + + installed successfully. + + + + + + is already installed on the server. + + + + + + +Already installed containers were found on the server. All installed containers have been added to the application + + + + + Server ' + + + + + ' was removed + + + + + All containers from server ' + + + + + has been removed from the server ' + + + + + Please login as the user + + + NotificationHandler @@ -198,156 +276,6 @@ - - OpenVpnSettings - - - VPN Addresses Subnet - - - - - Network protocol - - - - - Port - - - - - Auto-negotiate encryption - - - - - - Hash - - - - - SHA512 - - - - - SHA384 - - - - - SHA256 - - - - - SHA3-512 - - - - - SHA3-384 - - - - - SHA3-256 - - - - - whirlpool - - - - - BLAKE2b512 - - - - - BLAKE2s256 - - - - - SHA1 - - - - - - Cipher - - - - - AES-256-GCM - - - - - AES-192-GCM - - - - - AES-128-GCM - - - - - AES-256-CBC - - - - - AES-192-CBC - - - - - AES-128-CBC - - - - - ChaCha20-Poly1305 - - - - - ARIA-256-CBC - - - - - CAMELLIA-256-CBC - - - - - none - - - - - TLS auth - - - - - Block DNS requests outside of VPN - - - - - Additional configuration commands - - - PageAbout @@ -545,6 +473,11 @@ Removing services from + + + Usually it takes no more than 5 minutes + + PageGeneralSettings @@ -587,12 +520,12 @@ PageHome - - Протокол подключения + + VPN protocol - + Servers @@ -1049,6 +982,304 @@ If AmneziaDNS service is not installed on the same server, or this option is unc + + PageProtocolCloakSettings + + + Settings updated successfully + + + + + Cloak settings + + + + + Disguised as traffic from + + + + + Port + + + + + + Cipher + + + + + Save and Restart Amnezia + + + + + PageProtocolOpenVpnSettings + + + Settings updated successfully + + + + + OpenVPN settings + + + + + VPN Addresses Subnet + + + + + Network protocol + + + + + Port + + + + + Auto-negotiate encryption + + + + + + Hash + + + + + SHA512 + + + + + SHA384 + + + + + SHA256 + + + + + SHA3-512 + + + + + SHA3-384 + + + + + SHA3-256 + + + + + whirlpool + + + + + BLAKE2b512 + + + + + BLAKE2s256 + + + + + SHA1 + + + + + + Cipher + + + + + AES-256-GCM + + + + + AES-192-GCM + + + + + AES-128-GCM + + + + + AES-256-CBC + + + + + AES-192-CBC + + + + + AES-128-CBC + + + + + ChaCha20-Poly1305 + + + + + ARIA-256-CBC + + + + + CAMELLIA-256-CBC + + + + + none + + + + + TLS auth + + + + + Block DNS requests outside of VPN + + + + + Additional client configuration commands + + + + + + Commands: + + + + + Additional server configuration commands + + + + + Remove OpenVPN + + + + + Remove OpenVpn from server? + + + + + Continue + Продолжить + + + + Cancel + + + + + Save and Restart Amnezia + + + + + PageProtocolRaw + + + settings + + + + + Show connection options + + + + + Connection options + + + + + + Remove + + + + + from server? + + + + + Continue + Продолжить + + + + Cancel + + + + + PageProtocolShadowSocksSettings + + + Settings updated successfully + + + + + ShadowSocks settings + + + + + Port + + + + + + Cipher + + + + + Save and Restart Amnezia + + + PageQrDecoderIos @@ -1175,6 +1406,139 @@ If AmneziaDNS service is not installed on the same server, or this option is unc + + PageServiceSftpSettings + + + Settings updated successfully + + + + + SFTP settings + + + + + Host + + + + + Port + + + + + Login + + + + + Password + + + + + Mount folder on device + + + + + In order to mount remote SFTP folder as local drive, perform following steps: <br> + + + + + + <br>1. Install the latest version of + + + + + + <br>2. Install the latest version of + + + + + Detailed instructions + + + + + Remove SFTP and all data stored there + + + + + Some description + + + + + Continue + Продолжить + + + + Cancel + + + + + PageServiceTorWebsiteSettings + + + Settings updated successfully + + + + + Tor website settings + + + + + Website address + + + + + Use <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor Browser</a> to open this url. + + + + + After installation it takes several minutes while your onion site will become available in the Tor Network. + + + + + When configuring WordPress set the domain as this onion address. + + + + + Remove website + + + + + Some description + + + + + Continue + Продолжить + + + + Cancel + + + PageSettings @@ -1275,65 +1639,85 @@ And if you don't like the app, all the more support it - the donation will PageSettingsApplication - + Application - + Language - + + Logging + + + + + Enabled + + + + + Disabled + + + + Reset settings and remove all data from the application + + + Reset settings and remove all data from the application? + + + + + All settings will be reset to default. All installed AmneziaVPN services will still remain on the server. + + + + + Continue + Продолжить + + + + Cancel + + PageSettingsBackup - + Backup - - Save logs + + Settings restored from backup file - - Open folder with logs - - - - - Save logs to file - - - - - Clear logs - - - - + Configuration backup - + It will help you instantly restore connection settings at the next installation - + Make a backup - + Restore from backup @@ -1347,7 +1731,7 @@ And if you don't like the app, all the more support it - the donation will - Use AmnesiaDNS if installed on the server + Use AmneziaDNS if installed on the server @@ -1399,11 +1783,39 @@ And if you don't like the app, all the more support it - the donation will - + Save + + PageSettingsLogging + + + Logging + + + + + Save logs + + + + + Open folder with logs + + + + + Save logs to file + + + + + Clear logs + + + PageSettingsServerData @@ -1413,80 +1825,80 @@ And if you don't like the app, all the more support it - the donation will - Не найдено установленных контейнеров + No installed containers found - + Clear Amnezia cache - + May be needed when changing other settings - + Clear cached profiles? Очистить закешированные профили - + some description - - - + + + Continue Продолжить - - - + + + Cancel - - Проверить сервер на наличие ранее установленных сервисов Amnezia + + Check the server for previously installed Amnezia services - - Добавим их в приложение, если они не отображались + + Add them to the application if they were not displayed - + Remove server from application - + Remove server? - + All installed AmneziaVPN services will still remain on the server. - + Clear server from Amnezia software - + Clear server from Amnezia software? - + All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted. @@ -1499,26 +1911,63 @@ And if you don't like the app, all the more support it - the donation will - + Save - + Protocols - + Services - + Data + + PageSettingsServerProtocol + + + settings + + + + + + Remove + + + + + from server? + + + + + Continue + Продолжить + + + + Cancel + + + + + PageSettingsServersList + + + Servers + + + PageSetupWizard @@ -1568,6 +2017,41 @@ OpenVPN profile will be installed. + + PageSetupWizardConfigSource + + + Server connection + + + + + Do not use connection code from public sources. It may have been created to intercept your data. + +It's okay if a friend passed the code. + + + + + What do you have? + + + + + File with connection settings + + + + + QR-code + + + + + Key as text + + + PageSetupWizardCredentials @@ -1582,42 +2066,47 @@ OpenVPN profile will be installed. - + 255.255.255.255:88 + + + + + + Insert + + + + + Password / SSH private key + + + + + Continue + Продолжить + + + Enter the address in the format 255.255.255.255:88 - + Login to connect via SSH - - Password / Private key - - - - - Set up a server the easy way - - - - - Select protocol to install - - - - + Ip address cannot be empty - + Login cannot be empty - + Password/private key cannot be empty @@ -1625,12 +2114,22 @@ OpenVPN profile will be installed. PageSetupWizardEasy - + What is the level of internet control in your region? + Set up a VPN yourself + + + + + I want to choose a VPN protocol + + + + Continue Продолжить @@ -1670,14 +2169,18 @@ This protocol support exporting connection profiles to mobile devices by exporti PageSetupWizardInstalling - - - The container you are trying to install is already installed on the server. All installed containers have been added to the application + + The server has already been added to the application - - The server has already been added to the application + + Installing + + + + + Usually it takes no more than 5 minutes @@ -1751,31 +2254,72 @@ This protocol supports exporting connection profiles to mobile devices by using - + detailed protocol description - + Close - - Установить + + Network protocol + + + + + Port + + + + + Install + + + + + PageSetupWizardProtocols + + + VPN protocol + + + + + Choose the one with the highest priority for you. Later, you can install other protocols and additional services, such as DNS proxy and SFTP. + + + + + PageSetupWizardQrReader + + + Point the camera at the QR code and hold for a couple of seconds. PageSetupWizardStart + + + Free service for creating a personal VPN on your server. + + + + + Helps you access blocked content without revealing your privacy, even to VPN providers. + + - У меня есть данные для подключения + I have the data to connect - У меня ничего нет + I have nothing @@ -1837,27 +2381,27 @@ Please note, you should add addresses to the list after VPN connection establish PageSetupWizardViewConfig - + New connection - + Do not use connection code from public sources. It could be created to intercept your data. - + Collapse content - + Show content - + Connect @@ -1865,80 +2409,84 @@ Please note, you should add addresses to the list after VPN connection establish PageShare - - For the AmnesiaVPN app - - - - + OpenVpn native format - + WireGuard native format - + VPN Access - + Connection - - Full - - - - + VPN access without the ability to manage the server - + Full access to server - + Server and service - + Server - + + Accessing + + + + Protocols and services - - + Connection to - - + + File with connection settings to - - + + For the AmneziaVPN app + + + + + Full access + + + + + Connection format - + Share @@ -2298,87 +2846,6 @@ New encryption keys pair will be generated. - - PageTest - - - Протоколы - - - - - Сервисы - - - - - Данные - - - - - - Forget this server - - - - - SHA512 - - - - - SHA384 - - - - - SHA256 - - - - - SHA3-512 - - - - - SHA3-384 - - - - - SHA3-256 - - - - - whirlpool - - - - - BLAKE2b512 - - - - - BLAKE2s256 - - - - - SHA1 - - - - - - - Auto-negotiate encryption - - - PageVPN @@ -2470,7 +2937,7 @@ AmneziaVPN detected this profile may contain malicious scripts. Please, carefull PopupType - + Close @@ -2683,59 +3150,63 @@ AmneziaVPN detected this profile may contain malicious scripts. Please, carefull - + IPsec - - + + Web site in Tor network - - + DNS Service - + Sftp file sharing service - + + Amnezia DNS + + + + OpenVPN container - + Container with OpenVpn and ShadowSocks - + Container with OpenVpn and ShadowSocks protocols configured with traffic masking by Cloak plugin - + WireGuard container - + IPsec container - + Sftp file sharing service - is secure FTP service - + Sftp service @@ -2788,17 +3259,17 @@ AmneziaVPN detected this profile may contain malicious scripts. Please, carefull ServerContainersLogic - + Error occurred while configuring server. - + Error message: - + See logs for details. @@ -2807,17 +3278,17 @@ AmneziaVPN detected this profile may contain malicious scripts. Please, carefull ServerSettingsLogic - + Clear client cached profile - + Service: - + Cache cleared @@ -2825,13 +3296,13 @@ AmneziaVPN detected this profile may contain malicious scripts. Please, carefull Settings - + Server #1 - - + + Server @@ -2844,20 +3315,30 @@ AmneziaVPN detected this profile may contain malicious scripts. Please, carefull - + Save log - + Backup application config - + Open backup + + + Backup file is empty + + + + + Backup file is corrupted + + ShareConnectionButtonCopyType @@ -2875,22 +3356,22 @@ AmneziaVPN detected this profile may contain malicious scripts. Please, carefull ShareConnectionDrawer - - Save connection code + + Share - + Copy - + Show content - + To read the QR code in the Amnezia app, select "Add Server" → "I have connection details" @@ -2934,24 +3415,24 @@ AmneziaVPN detected this profile may contain malicious scripts. Please, carefull StartPageLogic - - + + Connect - - + + Please fill in all fields - + Connecting... - + Open config file @@ -2987,17 +3468,17 @@ AmneziaVPN detected this profile may contain malicious scripts. Please, carefull UiLogic - + Error occurred while configuring server. - + Error message: - + See logs for details. @@ -3081,27 +3562,27 @@ AmneziaVPN detected this profile may contain malicious scripts. Please, carefull amnezia::ContainerProps - + Low - + High - + Medium - + Many foreign websites and VPN providers are blocked - + Some foreign sites are blocked, but VPN providers are not blocked diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index be235e42..d6c32fac 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -392,3 +392,24 @@ void InstallController::mountSftpDrive(const QString &port, const QString &passw #endif } + +bool InstallController::checkSshConnection() +{ + ServerController serverController(m_settings); + + ErrorCode errorCode = ErrorCode::NoError; + QString output; + output = serverController.checkSshConnection(m_currentlyInstalledServerCredentials, &errorCode); + + if (errorCode != ErrorCode::NoError) { + emit installationErrorOccurred(errorString(errorCode)); + return false; + } else { + if (output.contains(tr("Please login as the user"))) { + output.replace("\n", ""); + emit installationErrorOccurred(output); + return false; + } + } + return true; +} diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index 8bc04f39..b25f2082 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -37,6 +37,8 @@ public slots: void mountSftpDrive(const QString &port, const QString &password, const QString &username); + bool checkSshConnection(); + signals: void installContainerFinished(QString finishMessage); void installServerFinished(QString finishMessage); diff --git a/client/ui/models/languageModel.cpp b/client/ui/models/languageModel.cpp index adbbdaaa..5135f348 100644 --- a/client/ui/models/languageModel.cpp +++ b/client/ui/models/languageModel.cpp @@ -55,7 +55,7 @@ int LanguageModel::getCurrentLanguageIndex() } } -QString LanguageModel::getCurrentLanuageName() +QString LanguageModel::getCurrentLanguageName() { return m_availableLanguages[getCurrentLanguageIndex()].name; } diff --git a/client/ui/models/languageModel.h b/client/ui/models/languageModel.h index 4e8a9092..b64862dd 100644 --- a/client/ui/models/languageModel.h +++ b/client/ui/models/languageModel.h @@ -43,13 +43,17 @@ public: int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + Q_PROPERTY(QString currentLanguageName READ getCurrentLanguageName NOTIFY translationsUpdated) + Q_PROPERTY(int currentLanguageIndex READ getCurrentLanguageIndex NOTIFY translationsUpdated) + public slots: void changeLanguage(const LanguageSettings::AvailableLanguageEnum language); int getCurrentLanguageIndex(); - QString getCurrentLanuageName(); + QString getCurrentLanguageName(); signals: void updateTranslations(const QLocale &locale); + void translationsUpdated(); protected: QHash roleNames() const override; diff --git a/client/ui/qml/Components/SelectLanguageDrawer.qml b/client/ui/qml/Components/SelectLanguageDrawer.qml index d872a889..d318aab8 100644 --- a/client/ui/qml/Components/SelectLanguageDrawer.qml +++ b/client/ui/qml/Components/SelectLanguageDrawer.qml @@ -59,7 +59,7 @@ DrawerType { interactive: false model: LanguageModel - currentIndex: LanguageModel.getCurrentLanguageIndex() + currentIndex: LanguageModel.currentLanguageIndex ButtonGroup { id: buttonGroup @@ -127,6 +127,7 @@ DrawerType { onClicked: { listView.currentIndex = index LanguageModel.changeLanguage(languageIndex) + root.close() } } } diff --git a/client/ui/qml/Controls2/PopupType.qml b/client/ui/qml/Controls2/PopupType.qml index e7bb16f4..e4d2a449 100644 --- a/client/ui/qml/Controls2/PopupType.qml +++ b/client/ui/qml/Controls2/PopupType.qml @@ -55,6 +55,8 @@ Popup { BasicButtonType { visible: closeButtonVisible + implicitHeight: 32 + defaultColor: "white" hoveredColor: "#C1C2C5" pressedColor: "#AEB0B7" diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml index 54c2ffb8..cc764451 100644 --- a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -17,13 +17,9 @@ PageType { Connections { target: InstallController - function onInstallationErrorOccurred(errorMessage) { - PageController.showErrorMessage(errorMessage) - } - function onUpdateContainerFinished() { //todo change to notification - PageController.showErrorMessage(qsTr("Settings updated successfully")) + PageController.showNotificationMessage(qsTr("Settings updated successfully")) } } diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index 26a9c495..7d38a2b0 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -18,13 +18,9 @@ PageType { Connections { target: InstallController - function onInstallationErrorOccurred(errorMessage) { - PageController.showErrorMessage(errorMessage) - } - function onUpdateContainerFinished() { //todo change to notification - PageController.showErrorMessage(qsTr("Settings updated successfully")) + PageController.showNotificationMessage(qsTr("Settings updated successfully")) } } diff --git a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml index d873beac..64390790 100644 --- a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml @@ -17,13 +17,9 @@ PageType { Connections { target: InstallController - function onInstallationErrorOccurred(errorMessage) { - PageController.showErrorMessage(errorMessage) - } - function onUpdateContainerFinished() { //todo change to notification - PageController.showErrorMessage(qsTr("Settings updated successfully")) + PageController.showNotificationMessage(qsTr("Settings updated successfully")) } } diff --git a/client/ui/qml/Pages2/PageServiceSftpSettings.qml b/client/ui/qml/Pages2/PageServiceSftpSettings.qml index 0eabb076..d37562a1 100644 --- a/client/ui/qml/Pages2/PageServiceSftpSettings.qml +++ b/client/ui/qml/Pages2/PageServiceSftpSettings.qml @@ -19,13 +19,9 @@ PageType { Connections { target: InstallController - function onInstallationErrorOccurred(errorMessage) { - PageController.showErrorMessage(errorMessage) - } - function onUpdateContainerFinished() { //todo change to notification - PageController.showErrorMessage(qsTr("Settings updated successfully")) + PageController.showNotificationMessage(qsTr("Settings updated successfully")) } } diff --git a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml index 7cf2a81a..5ddb9ed6 100644 --- a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml +++ b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml @@ -20,13 +20,9 @@ PageType { Connections { target: InstallController - function onInstallationErrorOccurred(errorMessage) { - PageController.showErrorMessage(errorMessage) - } - function onUpdateContainerFinished() { //todo change to notification - PageController.showErrorMessage(qsTr("Settings updated successfully")) + PageController.showNotificationMessage(qsTr("Settings updated successfully")) } } diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index 2c7d8601..2001e892 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -48,7 +48,7 @@ PageType { Layout.topMargin: 16 text: qsTr("Language") - descriptionText: LanguageModel.getCurrentLanuageName() + descriptionText: LanguageModel.currentLanguageName rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 3aec4242..12e66269 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -28,11 +28,6 @@ PageType { PageController.showErrorMessage(message) } - function onInstallationErrorOccurred(errorMessage) { - closePage() // close deInstalling page - PageController.showErrorMessage(errorMessage) - } - function onRemoveCurrentlyProcessedServerFinished(finishedMessage) { if (!ServersModel.getServersCount()) { PageController.replaceStartPage() diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index cc1197ad..dab860c1 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -94,7 +94,7 @@ PageType { Layout.fillWidth: true Layout.topMargin: 24 - text: qsTr("Set up a server the easy way") + text: qsTr("Continue") onClicked: function() { if (!isCredentialsFilled()) { @@ -104,34 +104,41 @@ PageType { InstallController.setShouldCreateServer(true) InstallController.setCurrentlyInstalledServerCredentials(hostname.textField.text, username.textField.text, secretData.textField.text) + PageController.showBusyIndicator(true) + var isConnectionOpened = InstallController.checkSshConnection() + PageController.showBusyIndicator(false) + if (!isConnectionOpened) { + return + } + goToPage(PageEnum.PageSetupWizardEasy) } } - BasicButtonType { - Layout.fillWidth: true - Layout.topMargin: -8 +// 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 +// 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("Select protocol to install") +// text: qsTr("Select protocol to install") - onClicked: function() { - if (!isCredentialsFilled()) { - return - } +// onClicked: function() { +// if (!isCredentialsFilled()) { +// return +// } - InstallController.setShouldCreateServer(true) - InstallController.setCurrentlyInstalledServerCredentials(hostname.textField.text, username.textField.text, secretData.textField.text) +// InstallController.setShouldCreateServer(true) +// InstallController.setCurrentlyInstalledServerCredentials(hostname.textField.text, username.textField.text, secretData.textField.text) - goToPage(PageEnum.PageSetupWizardProtocols) - } - } +// goToPage(PageEnum.PageSetupWizardProtocols) +// } +// } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index 2a6e1909..ac1c3a44 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -15,6 +15,8 @@ import "../Config" PageType { id: root + property bool isEasySetup: true + SortFilterProxyModel { id: proxyContainersModel sourceModel: ContainersModel @@ -64,6 +66,10 @@ PageType { headerText: qsTr("What is the level of internet control in your region?") } + ButtonGroup { + id: buttonGroup + } + ListView { id: containers width: parent.width @@ -101,6 +107,7 @@ PageType { ButtonGroup.group: buttonGroup onClicked: function() { + isEasySetup = true var defaultContainerProto = ContainerProps.defaultProtocol(dockerContainer) containers.dockerContainer = dockerContainer @@ -117,9 +124,18 @@ PageType { } } } + } - ButtonGroup { - id: buttonGroup + CardType { + implicitWidth: parent.width + + headerText: qsTr("Set up a VPN yourself") + bodyText: qsTr("I want to choose a VPN protocol") + + ButtonGroup.group: buttonGroup + + onClicked: function() { + isEasySetup = false } } @@ -132,11 +148,15 @@ PageType { text: qsTr("Continue") onClicked: function() { - ContainersModel.setCurrentlyProcessedContainerIndex(containers.dockerContainer) - goToPage(PageEnum.PageSetupWizardInstalling); - InstallController.install(containers.dockerContainer, - containers.containerDefaultPort, - containers.containerDefaultTransportProto) + if (root.isEasySetup) { + ContainersModel.setCurrentlyProcessedContainerIndex(containers.dockerContainer) + goToPage(PageEnum.PageSetupWizardInstalling); + InstallController.install(containers.dockerContainer, + containers.containerDefaultPort, + containers.containerDefaultTransportProto) + } else { + goToPage(PageEnum.PageSetupWizardProtocols) + } } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index f254a474..6c6d1a67 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -17,11 +17,6 @@ PageType { Connections { target: InstallController - function onInstallationErrorOccurred(errorMessage) { - closePage() - PageController.showErrorMessage(errorMessage) - } - function onInstallContainerFinished(finishedMessage) { goToStartPage() if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) { diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index eff17ab5..11d7ba29 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -85,8 +85,7 @@ PageType { text: qsTr("I have nothing") - onClicked: { - } + onClicked: Qt.openUrlExternally("https://ru-docs.amnezia.org/guides/hosting-instructions") } } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 811fc923..ec29b314 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -45,6 +45,26 @@ PageType { } } + Connections { + target: InstallController + + function onInstallationErrorOccurred(errorMessage) { + PageController.showErrorMessage(errorMessage) + + var needCloseCurrentPage = false + var currentPageName = stackView.currentItem.objectName + + if (currentPageName === PageController.getPagePath(PageEnum.PageSetupWizardInstalling)) { + needCloseCurrentPage = true + } else if (currentPageName === PageController.getPagePath(PageEnum.PageDeinstalling)) { + needCloseCurrentPage = true + } + if (needCloseCurrentPage) { + PageController.closePage() + } + } + } + StackViewType { id: tabBarStackView From 925fd9f26805d03d53f2473993abc45d561734a8 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 1 Aug 2023 11:06:46 +0900 Subject: [PATCH 053/278] added display of installed services on the page PageSettingsServersList --- client/ui/models/containers_model.cpp | 19 +++++++++++++++++++ client/ui/models/containers_model.h | 1 + .../ui/qml/Pages2/PageSettingsServersList.qml | 10 +++++++++- .../qml/Pages2/PageSetupWizardViewConfig.qml | 6 +++++- 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index ea571a22..639cf962 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -129,6 +129,25 @@ QJsonObject ContainersModel::getCurrentlyProcessedContainerConfig() return qvariant_cast(data(index(m_currentlyProcessedContainerIndex), ConfigRole)); } +QStringList ContainersModel::getAllInstalledServicesName(const int serverIndex) +{ + QStringList servicesName; + const auto &containers = m_settings->containers(serverIndex); + for (const DockerContainer &container : containers.keys()) { + if (ContainerProps::containerService(container) == ServiceType::Other && m_containers.contains(container)) { + if (container == DockerContainer::Dns) { + servicesName.append("DNS"); + } else if (container == DockerContainer::Sftp) { + servicesName.append("SFTP"); + } else if (container == DockerContainer::TorWebSite) { + servicesName.append("TOR"); + } + } + } + servicesName.sort(); + return servicesName; +} + ErrorCode ContainersModel::removeAllContainers() { diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index 690eff41..a905890a 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -51,6 +51,7 @@ public slots: QString getCurrentlyProcessedContainerName(); QJsonObject getCurrentlyProcessedContainerConfig(); + QStringList getAllInstalledServicesName(const int serverIndex); ErrorCode removeAllContainers(); ErrorCode removeCurrentlyProcessedContainer(); diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index 496ed370..40e51e9e 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -85,7 +85,15 @@ PageType { Layout.fillWidth: true text: name - descriptionText: hostName + descriptionText: { + var servicesNameString = "" + var servicesName = ContainersModel.getAllInstalledServicesName(index) + for (var i = 0; i < servicesName.length; i++) { + servicesNameString += servicesName[i] + " · " + } + + return servicesNameString + hostName + } rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { diff --git a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml index 372a5cde..5f52a5ba 100644 --- a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml +++ b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml @@ -91,11 +91,15 @@ PageType { } BasicButtonType { + Layout.topMargin: 16 + Layout.leftMargin: -8 + implicitHeight: 32 + defaultColor: "transparent" hoveredColor: Qt.rgba(1, 1, 1, 0.08) pressedColor: Qt.rgba(1, 1, 1, 0.12) disabledColor: "#878B91" - textColor: "#D7D8DB" + textColor: "#FBB26A" text: showContent ? qsTr("Collapse content") : qsTr("Show content") From ebcca0c3b8802c294b69065ee0a13c69bf2528ef Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 2 Aug 2023 20:37:43 +0900 Subject: [PATCH 054/278] added processing of private ssh keys --- client/amnezia_application.cpp | 4 + client/ui/controllers/installController.cpp | 32 +++++++- client/ui/controllers/installController.h | 7 ++ client/ui/controllers/pageController.h | 3 + client/ui/models/containers_model.cpp | 5 ++ client/ui/models/containers_model.h | 2 + .../Components/SettingsContainersListView.qml | 12 ++- .../ui/qml/Pages2/PageSettingsServerData.qml | 4 +- client/ui/qml/Pages2/PageStart.qml | 2 +- client/ui/qml/main2.qml | 74 +++++++++++++++++++ 10 files changed, 137 insertions(+), 8 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 7d7a0376..38126e43 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -315,6 +315,10 @@ void AmneziaApplication::initControllers() m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_settings)); m_engine->rootContext()->setContextProperty("InstallController", m_installController.get()); + connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(), + &PageController::showPassphraseRequestDrawer); + connect(m_pageController.get(), &PageController::passphraseRequestDrawerClosed, m_installController.get(), + &InstallController::setEncryptedPassphrase); m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings)); m_engine->rootContext()->setContextProperty("ImportController", m_importController.get()); diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index d6c32fac..49c77708 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -1,6 +1,7 @@ #include "installController.h" #include +#include #include #include @@ -396,8 +397,31 @@ void InstallController::mountSftpDrive(const QString &port, const QString &passw bool InstallController::checkSshConnection() { ServerController serverController(m_settings); - ErrorCode errorCode = ErrorCode::NoError; + m_privateKeyPassphrase = ""; + + if (m_currentlyInstalledServerCredentials.secretData.contains("BEGIN") + && m_currentlyInstalledServerCredentials.secretData.contains("PRIVATE KEY")) { + auto passphraseCallback = [this]() { + emit passphraseRequestStarted(); + QEventLoop loop; + QObject::connect(this, &InstallController::passphraseRequestFinished, &loop, &QEventLoop::quit); + loop.exec(); + + return m_privateKeyPassphrase; + }; + + QString decryptedPrivateKey; + errorCode = serverController.getDecryptedPrivateKey(m_currentlyInstalledServerCredentials, decryptedPrivateKey, + passphraseCallback); + if (errorCode == ErrorCode::NoError) { + m_currentlyInstalledServerCredentials.secretData = decryptedPrivateKey; + } else { + emit installationErrorOccurred(errorString(errorCode)); + return false; + } + } + QString output; output = serverController.checkSshConnection(m_currentlyInstalledServerCredentials, &errorCode); @@ -413,3 +437,9 @@ bool InstallController::checkSshConnection() } return true; } + +void InstallController::setEncryptedPassphrase(QString passphrase) +{ + m_privateKeyPassphrase = passphrase; + emit passphraseRequestFinished(); +} diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index b25f2082..54bcda31 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -39,6 +39,8 @@ public slots: bool checkSshConnection(); + void setEncryptedPassphrase(QString passphrase); + signals: void installContainerFinished(QString finishMessage); void installServerFinished(QString finishMessage); @@ -55,6 +57,9 @@ signals: void serverAlreadyExists(int serverIndex); + void passphraseRequestStarted(); + void passphraseRequestFinished(); + private: void installServer(DockerContainer container, QJsonObject &config); void installContainer(DockerContainer container, QJsonObject &config); @@ -68,6 +73,8 @@ private: bool m_shouldCreateServer; + QString m_privateKeyPassphrase; + #ifndef Q_OS_IOS QList> m_sftpMountProcesses; #endif diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 4273ed25..8185b525 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -91,6 +91,9 @@ signals: void hideMainWindow(); void raiseMainWindow(); + void showPassphraseRequestDrawer(); + void passphraseRequestDrawerClosed(QString passphrase); + private: QSharedPointer m_serversModel; }; diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index 639cf962..8d24e019 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -211,6 +211,11 @@ bool ContainersModel::isAmneziaDnsContainerInstalled(const int serverIndex) return containers.contains(DockerContainer::Dns); } +// bool ContainersModel::isOnlyServicesInstalled(const int serverIndex) +//{ + +//} + QHash ContainersModel::roleNames() const { QHash roles; diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index a905890a..8978315c 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -60,6 +60,8 @@ public slots: bool isAmneziaDnsContainerInstalled(); bool isAmneziaDnsContainerInstalled(const int serverIndex); + // bool isOnlyServicesInstalled(const int serverIndex); + protected: QHash roleNames() const override; diff --git a/client/ui/qml/Components/SettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml index eac473f4..bb578899 100644 --- a/client/ui/qml/Components/SettingsContainersListView.qml +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -106,13 +106,17 @@ ListView { break } case ContainerEnum.WireGuard: { - WireGuardConfigModel.updateModel(config) - goToPage(PageEnum.PageProtocolWireGuardSettings) + ProtocolsModel.updateModel(config) + goToPage(PageEnum.PageProtocolRaw) +// WireGuardConfigModel.updateModel(config) +// goToPage(PageEnum.PageProtocolWireGuardSettings) break } case ContainerEnum.Ipsec: { - Ikev2ConfigModel.updateModel(config) - goToPage(PageEnum.PageProtocolIKev2Settings) + ProtocolsModel.updateModel(config) + goToPage(PageEnum.PageProtocolRaw) +// Ikev2ConfigModel.updateModel(config) +// goToPage(PageEnum.PageProtocolIKev2Settings) break } case ContainerEnum.Sftp: { diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 12e66269..7be35cac 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -134,7 +134,7 @@ PageType { questionDrawer.yesButtonFunction = function() { questionDrawer.visible = false PageController.showBusyIndicator(true) - if (ServersModel.isDefaultServerCurrentlyProcessed && ConnectionController.isConnected) { + if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { ConnectionController.closeConnection() } InstallController.removeCurrentlyProcessedServer() @@ -165,7 +165,7 @@ PageType { questionDrawer.yesButtonFunction = function() { questionDrawer.visible = false goToPage(PageEnum.PageDeinstalling) - if (ServersModel.isDefaultServerCurrentlyProcessed && ConnectionController.isConnected) { + if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { ConnectionController.closeVpnConnection() } InstallController.removeAllContainers() diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index ec29b314..2cc64a91 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -52,7 +52,7 @@ PageType { PageController.showErrorMessage(errorMessage) var needCloseCurrentPage = false - var currentPageName = stackView.currentItem.objectName + var currentPageName = tabBarStackView.currentItem.objectName if (currentPageName === PageController.getPagePath(PageEnum.PageSetupWizardInstalling)) { needCloseCurrentPage = true diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index 6b2bee2a..f07bcf5d 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -75,6 +75,10 @@ Window { popupNotificationMessage.open() popupNotificationTimer.start() } + + function onShowPassphraseRequestDrawer() { + privateKeyPassphraseDrawer.open() + } } Item { @@ -111,4 +115,74 @@ Window { id: popupErrorMessage } } + + Item { + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + + implicitHeight: popupErrorMessage.height + + DrawerType { + id: privateKeyPassphraseDrawer + + width: root.width + height: root.height * 0.35 + + onVisibleChanged: { + if (privateKeyPassphraseDrawer.visible) { + passphrase.textFieldText = "" + passphrase.textField.forceActiveFocus() + } + } + onAboutToHide: { + PageController.showBusyIndicator(true) + } + onAboutToShow: { + PageController.showBusyIndicator(false) + } + + ColumnLayout { + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + TextFieldWithHeaderType { + id: passphrase + + property bool hidePassword: true + + Layout.fillWidth: true + headerText: qsTr("Private key passphrase") + textField.echoMode: hidePassword ? TextInput.Password : TextInput.Normal + buttonImageSource: hidePassword ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg" + + clickedFunc: function() { + hidePassword = !hidePassword + } + } + + BasicButtonType { + Layout.fillWidth: true + + 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("Save") + + onClicked: { + privateKeyPassphraseDrawer.close() + PageController.passphraseRequestDrawerClosed(passphrase.textFieldText) + } + } + } + } + } } From 2c429fd406461abd211b531575269c191dc6d755 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 2 Aug 2023 21:46:02 +0900 Subject: [PATCH 055/278] added removal of spaces when inserting ip addresses - fixed server sharing when sharing a server available only for connection when choosing a server with full access - removed the notification about an empty backup file when the user closes the file dialog without selecting anything --- client/ui/controllers/settingsController.cpp | 1 - .../ui/qml/Pages2/PageSetupWizardCredentials.qml | 15 ++++----------- client/ui/qml/Pages2/PageShare.qml | 2 +- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index eda67919..6a83dcb1 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -85,7 +85,6 @@ void SettingsController::restoreAppConfig() QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.backup"); if (fileName.isEmpty()) { - emit changeSettingsErrorOccurred(tr("Backup file is empty")); return; } diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index dab860c1..5187a6e3 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -53,11 +53,9 @@ PageType { textField.validator: RegularExpressionValidator { regularExpression: InstallController.ipAddressPortRegExp() } - buttonText: qsTr("Insert") - clickedFunc: function() { - textField.text = "" - textField.paste() + onTextFieldTextChanged: { + textField.text = textField.text.replace(/^\s+|\s+$/g, ''); } } @@ -67,12 +65,6 @@ PageType { Layout.fillWidth: true headerText: qsTr("Login to connect via SSH") textFieldPlaceholderText: "root" - buttonText: qsTr("Insert") - - clickedFunc: function() { - textField.text = "" - textField.paste() - } } TextFieldWithHeaderType { @@ -83,7 +75,8 @@ PageType { Layout.fillWidth: true headerText: qsTr("Password / SSH private key") textField.echoMode: hidePassword ? TextInput.Password : TextInput.Normal - buttonImageSource: hidePassword ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg" + buttonImageSource: textFieldText !== "" ? (hidePassword ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg") + : "" clickedFunc: function() { hidePassword = !hidePassword diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index ca9dedd4..6e3fecf2 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -208,7 +208,7 @@ PageType { serverSelector.text = selectedText root.fullConfigServerSelectorText = selectedText root.connectionServerSelectorText = selectedText - ServersModel.currentlyProcessedIndex = currentIndex + ServersModel.currentlyProcessedIndex = proxyServersModel.mapToSource(currentIndex) } } From 90ae0b3e44689be23013ce63955170d18fc8bf9e Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 8 Aug 2023 19:10:14 +0500 Subject: [PATCH 056/278] added PageSettingsSplitTunneling - added a call to the context menu when clicking the right mouse button for textInput --- client/amnezia_application.cpp | 25 +- client/amnezia_application.h | 4 + client/images/controls/more-vertical.svg | 5 + client/images/controls/trash.svg | 5 + client/resources.qrc | 5 + client/settings.cpp | 10 +- client/settings.h | 3 +- client/ui/Controls2 | 34 ++ client/ui/controllers/connectionController.h | 12 +- client/ui/controllers/exportController.h | 2 +- client/ui/controllers/importController.h | 2 +- client/ui/controllers/installController.h | 12 +- client/ui/controllers/pageController.h | 5 +- client/ui/controllers/settingsController.cpp | 14 +- client/ui/controllers/settingsController.h | 11 +- client/ui/controllers/sitesController.cpp | 149 +++++++ client/ui/controllers/sitesController.h | 36 ++ client/ui/models/sites_model.cpp | 137 ++++--- client/ui/models/sites_model.h | 31 +- client/ui/pages_logic/SitesLogic.cpp | 70 ++-- client/ui/qml/Controls2/BasicButtonType.qml | 28 ++ client/ui/qml/Controls2/ContextMenuType.qml | 33 ++ client/ui/qml/Controls2/DropDownType.qml | 14 +- client/ui/qml/Controls2/TextAreaType.qml | 70 ++++ .../qml/Controls2/TextFieldWithHeaderType.qml | 42 +- client/ui/qml/Pages2/PageHome.qml | 5 +- .../Pages2/PageProtocolOpenVpnSettings.qml | 88 +--- .../ui/qml/Pages2/PageSettingsApplication.qml | 2 +- .../ui/qml/Pages2/PageSettingsConnection.qml | 21 +- client/ui/qml/Pages2/PageSettingsLogging.qml | 6 +- .../qml/Pages2/PageSettingsSplitTunneling.qml | 377 ++++++++++++++++++ 31 files changed, 1018 insertions(+), 240 deletions(-) create mode 100644 client/images/controls/more-vertical.svg create mode 100644 client/images/controls/trash.svg create mode 100644 client/ui/Controls2 create mode 100644 client/ui/controllers/sitesController.cpp create mode 100644 client/ui/controllers/sitesController.h create mode 100644 client/ui/qml/Controls2/ContextMenuType.qml create mode 100644 client/ui/qml/Controls2/TextAreaType.qml create mode 100644 client/ui/qml/Pages2/PageSettingsSplitTunneling.qml diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 38126e43..933a6346 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -146,16 +146,15 @@ void AmneziaApplication::init() // m_uiLogic->showOnStartup(); // #endif -// // TODO - fix -// #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) -// if (isPrimary()) { -// QObject::connect(this, &SingleApplication::instanceStarted, m_uiLogic, [this](){ -// qDebug() << "Secondary instance started, showing this window instead"; -// emit m_uiLogic->show(); -// emit m_uiLogic->raise(); -// }); -// } -// #endif + // TODO - fix +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) + if (isPrimary()) { + QObject::connect(this, &SingleApplication::instanceStarted, m_pageController.get(), [this]() { + qDebug() << "Secondary instance started, showing this window instead"; + emit m_pageController->raiseMainWindow(); + }); + } +#endif // Android TextField clipboard workaround // https://bugreports.qt.io/browse/QTBUG-113461 @@ -281,6 +280,9 @@ void AmneziaApplication::initModels() connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &AmneziaApplication::updateTranslator); connect(this, &AmneziaApplication::translationsUpdated, m_languageModel.get(), &LanguageModel::translationsUpdated); + m_sitesModel.reset(new SitesModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get()); + m_protocolsModel.reset(new ProtocolsModel(m_settings, this)); m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get()); @@ -328,4 +330,7 @@ void AmneziaApplication::initControllers() m_settingsController.reset(new SettingsController(m_serversModel, m_containersModel, m_settings)); m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get()); + + m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel)); + m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get()); } diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 2a10aa86..81897c83 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -19,6 +19,7 @@ #include "ui/controllers/installController.h" #include "ui/controllers/pageController.h" #include "ui/controllers/settingsController.h" +#include "ui/controllers/sitesController.h" #include "ui/models/containers_model.h" #include "ui/models/languageModel.h" #include "ui/models/protocols/cloakConfigModel.h" @@ -32,6 +33,7 @@ #include "ui/models/protocols_model.h" #include "ui/models/servers_model.h" #include "ui/models/services/sftpConfigModel.h" +#include "ui/models/sites_model.h" #define amnApp (static_cast(QCoreApplication::instance())) @@ -86,6 +88,7 @@ private: QSharedPointer m_serversModel; QScopedPointer m_languageModel; QScopedPointer m_protocolsModel; + QSharedPointer m_sitesModel; QScopedPointer m_openVpnConfigModel; QScopedPointer m_shadowSocksConfigModel; @@ -106,6 +109,7 @@ private: QScopedPointer m_importController; QScopedPointer m_exportController; QScopedPointer m_settingsController; + QScopedPointer m_sitesController; }; #endif // AMNEZIA_APPLICATION_H diff --git a/client/images/controls/more-vertical.svg b/client/images/controls/more-vertical.svg new file mode 100644 index 00000000..2110125d --- /dev/null +++ b/client/images/controls/more-vertical.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/images/controls/trash.svg b/client/images/controls/trash.svg new file mode 100644 index 00000000..5f2f08bf --- /dev/null +++ b/client/images/controls/trash.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/resources.qrc b/client/resources.qrc index 625292c2..2238f25d 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -280,5 +280,10 @@ ui/qml/Pages2/PageSetupWizardQrReader.qml images/controls/eye.svg images/controls/eye-off.svg + ui/qml/Pages2/PageSettingsSplitTunneling.qml + ui/qml/Controls2/ContextMenuType.qml + ui/qml/Controls2/TextAreaType.qml + images/controls/trash.svg + images/controls/more-vertical.svg diff --git a/client/settings.cpp b/client/settings.cpp index fbdd63ce..af1fbf55 100644 --- a/client/settings.cpp +++ b/client/settings.cpp @@ -231,14 +231,15 @@ QString Settings::routeModeString(RouteMode mode) const } } -void Settings::addVpnSite(RouteMode mode, const QString &site, const QString &ip) +bool Settings::addVpnSite(RouteMode mode, const QString &site, const QString &ip) { QVariantMap sites = vpnSites(mode); if (sites.contains(site) && ip.isEmpty()) - return; + return false; sites.insert(site, ip); setVpnSites(mode, sites); + return true; } void Settings::addVpnSites(RouteMode mode, const QMap &sites) @@ -308,6 +309,11 @@ void Settings::removeVpnSites(RouteMode mode, const QStringList &sites) setVpnSites(mode, sitesMap); } +void Settings::removeAllVpnSites(RouteMode mode) +{ + setVpnSites(mode, QVariantMap()); +} + QString Settings::primaryDns() const { return m_settings.value("Conf/primaryDns", cloudFlareNs1).toString(); diff --git a/client/settings.h b/client/settings.h index 00b02c15..d7737347 100644 --- a/client/settings.h +++ b/client/settings.h @@ -127,13 +127,14 @@ public: m_settings.setValue("Conf/" + routeModeString(mode), sites); m_settings.sync(); } - void addVpnSite(RouteMode mode, const QString &site, const QString &ip = ""); + bool addVpnSite(RouteMode mode, const QString &site, const QString &ip = ""); void addVpnSites(RouteMode mode, const QMap &sites); // map QStringList getVpnIps(RouteMode mode) const; void removeVpnSite(RouteMode mode, const QString &site); void addVpnIps(RouteMode mode, const QStringList &ip); void removeVpnSites(RouteMode mode, const QStringList &sites); + void removeAllVpnSites(RouteMode mode); bool useAmneziaDns() const { diff --git a/client/ui/Controls2 b/client/ui/Controls2 new file mode 100644 index 00000000..13f01bb7 --- /dev/null +++ b/client/ui/Controls2 @@ -0,0 +1,34 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +TextArea { + id: root + + width: parent.width + + topPadding: 16 + leftPadding: 16 + + color: "#D7D8DB" + selectionColor: "#412102" + selectedTextColor: "#D7D8DB" + placeholderTextColor: "#878B91" + + font.pixelSize: 16 + font.weight: Font.Medium + font.family: "PT Root UI VF" + + wrapMode: Text.Wrap + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton + onClicked: contextMenu.open() + } + + ContextMenuType { + id: contextMenu + textObj: textField + } +} diff --git a/client/ui/controllers/connectionController.h b/client/ui/controllers/connectionController.h index 421ae84f..a33d30c2 100644 --- a/client/ui/controllers/connectionController.h +++ b/client/ui/controllers/connectionController.h @@ -1,9 +1,9 @@ #ifndef CONNECTIONCONTROLLER_H #define CONNECTIONCONTROLLER_H -#include "ui/models/servers_model.h" -#include "ui/models/containers_model.h" #include "protocols/vpnprotocol.h" +#include "ui/models/containers_model.h" +#include "ui/models/servers_model.h" #include "vpnconnection.h" class ConnectionController : public QObject @@ -17,8 +17,7 @@ public: explicit ConnectionController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, - const QSharedPointer &vpnConnection, - QObject *parent = nullptr); + const QSharedPointer &vpnConnection, QObject *parent = nullptr); bool isConnected() const; bool isConnectionInProgress() const; @@ -32,11 +31,12 @@ public slots: void onConnectionStateChanged(Vpn::ConnectionState state); signals: - void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig); + void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig); void disconnectFromVpn(); void connectionStateChanged(); - void connectionErrorOccurred(QString errorMessage); + void connectionErrorOccurred(const QString &errorMessage); private: QSharedPointer m_serversModel; diff --git a/client/ui/controllers/exportController.h b/client/ui/controllers/exportController.h index 84575079..ce952096 100644 --- a/client/ui/controllers/exportController.h +++ b/client/ui/controllers/exportController.h @@ -39,7 +39,7 @@ public slots: signals: void generateConfig(int type); - void exportErrorOccurred(QString errorMessage); + void exportErrorOccurred(const QString &errorMessage); void exportConfigChanged(); diff --git a/client/ui/controllers/importController.h b/client/ui/controllers/importController.h index 3bdb2252..9556bc6a 100644 --- a/client/ui/controllers/importController.h +++ b/client/ui/controllers/importController.h @@ -34,7 +34,7 @@ public slots: signals: void importFinished(); - void importErrorOccurred(QString errorMessage); + void importErrorOccurred(const QString &errorMessage); void qrDecodingFinished(); diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index 54bcda31..38e2e780 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -42,18 +42,18 @@ public slots: void setEncryptedPassphrase(QString passphrase); signals: - void installContainerFinished(QString finishMessage); - void installServerFinished(QString finishMessage); + void installContainerFinished(const QString &finishMessage); + void installServerFinished(const QString &finishMessage); void updateContainerFinished(); void scanServerFinished(bool isInstalledContainerFound); - void removeCurrentlyProcessedServerFinished(QString finishedMessage); - void removeAllContainersFinished(QString finishedMessage); - void removeCurrentlyProcessedContainerFinished(QString finishedMessage); + void removeCurrentlyProcessedServerFinished(const QString &finishedMessage); + void removeAllContainersFinished(const QString &finishedMessage); + void removeCurrentlyProcessedContainerFinished(const QString &finishedMessage); - void installationErrorOccurred(QString errorMessage); + void installationErrorOccurred(const QString &errorMessage); void serverAlreadyExists(int serverIndex); diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 8185b525..fd748347 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -28,6 +28,7 @@ namespace PageLoader PageSettingsBackup, PageSettingsAbout, PageSettingsLogging, + PageSettingsSplitTunneling, PageServiceSftpSettings, PageServiceTorWebsiteSettings, @@ -83,8 +84,8 @@ signals: void restorePageHomeState(bool isContainerInstalled = false); void replaceStartPage(); - void showErrorMessage(QString errorMessage); - void showNotificationMessage(QString message); + void showErrorMessage(const QString &errorMessage); + void showNotificationMessage(const QString &message); void showBusyIndicator(bool visible); diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index 6a83dcb1..571b03e8 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -14,7 +14,7 @@ SettingsController::SettingsController(const QSharedPointer &serve m_appVersion = QString("%1: %2 (%3)").arg(tr("Software version"), QString(APP_MAJOR_VERSION), __DATE__); } -void SettingsController::setAmneziaDns(bool enable) +void SettingsController::toggleAmneziaDns(bool enable) { m_settings->setUseAmneziaDns(enable); } @@ -46,7 +46,7 @@ void SettingsController::setSecondaryDns(const QString &dns) emit secondaryDnsChanged(); } -bool SettingsController::isLoggingEnable() +bool SettingsController::isLoggingEnabled() { return m_settings->isSaveLogs(); } @@ -111,3 +111,13 @@ void SettingsController::clearSettings() m_settings->clearSettings(); m_serversModel->resetModel(); } + +bool SettingsController::isAutoConnectEnabled() +{ + return m_settings->isAutoConnect(); +} + +void SettingsController::toggleAutoConnect(bool enable) +{ + m_settings->setAutoConnect(enable); +} diff --git a/client/ui/controllers/settingsController.h b/client/ui/controllers/settingsController.h index ac85d300..ffd72f69 100644 --- a/client/ui/controllers/settingsController.h +++ b/client/ui/controllers/settingsController.h @@ -16,10 +16,10 @@ public: Q_PROPERTY(QString primaryDns READ getPrimaryDns WRITE setPrimaryDns NOTIFY primaryDnsChanged) Q_PROPERTY(QString secondaryDns READ getSecondaryDns WRITE setSecondaryDns NOTIFY secondaryDnsChanged) - Q_PROPERTY(bool isLoggingEnable READ isLoggingEnable WRITE toggleLogging NOTIFY loggingStateChanged) + Q_PROPERTY(bool isLoggingEnabled READ isLoggingEnabled WRITE toggleLogging NOTIFY loggingStateChanged) public slots: - void setAmneziaDns(bool enable); + void toggleAmneziaDns(bool enable); bool isAmneziaDnsEnabled(); QString getPrimaryDns(); @@ -28,7 +28,7 @@ public slots: QString getSecondaryDns(); void setSecondaryDns(const QString &dns); - bool isLoggingEnable(); + bool isLoggingEnabled(); void toggleLogging(bool enable); void openLogsFolder(); @@ -42,13 +42,16 @@ public slots: void clearSettings(); + bool isAutoConnectEnabled(); + void toggleAutoConnect(bool enable); + signals: void primaryDnsChanged(); void secondaryDnsChanged(); void loggingStateChanged(); void restoreBackupFinished(); - void changeSettingsErrorOccurred(QString errorMessage); + void changeSettingsErrorOccurred(const QString &errorMessage); private: QSharedPointer m_serversModel; diff --git a/client/ui/controllers/sitesController.cpp b/client/ui/controllers/sitesController.cpp new file mode 100644 index 00000000..7f3f93e1 --- /dev/null +++ b/client/ui/controllers/sitesController.cpp @@ -0,0 +1,149 @@ +#include "sitesController.h" + +#include + +#include "utilities.h" + +SitesController::SitesController(const std::shared_ptr &settings, + const QSharedPointer &vpnConnection, + const QSharedPointer &sitesModel, QObject *parent) + : QObject(parent), m_settings(settings), m_vpnConnection(vpnConnection), m_sitesModel(sitesModel) +{ +} + +void SitesController::addSite(QString hostname) +{ + if (hostname.isEmpty()) { + return; + } + + if (!hostname.contains(".")) { + emit errorOccurred(tr("Hostname not look like ip adress or domain name")); + return; + } + + if (!Utils::ipAddressWithSubnetRegExp().exactMatch(hostname)) { + // get domain name if it present + hostname.replace("https://", ""); + hostname.replace("http://", ""); + hostname.replace("ftp://", ""); + + hostname = hostname.split("/", Qt::SkipEmptyParts).first(); + } + + const auto &processSite = [this](const QString &hostname, const QString &ip) { + m_sitesModel->addSite(hostname, ip); + + if (!ip.isEmpty()) { + m_vpnConnection->addRoutes(QStringList() << ip); + m_vpnConnection->flushDns(); + } else if (Utils::ipAddressWithSubnetRegExp().exactMatch(hostname)) { + m_vpnConnection->addRoutes(QStringList() << hostname); + m_vpnConnection->flushDns(); + } + }; + + const auto &resolveCallback = [this, processSite](const QHostInfo &hostInfo) { + const QList &addresses = hostInfo.addresses(); + for (const QHostAddress &addr : hostInfo.addresses()) { + if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) { + processSite(hostInfo.hostName(), addr.toString()); + break; + } + } + }; + + if (Utils::ipAddressWithSubnetRegExp().exactMatch(hostname)) { + processSite(hostname, ""); + } else { + processSite(hostname, ""); + QHostInfo::lookupHost(hostname, this, resolveCallback); + } + + emit finished(tr("New site added: ") + hostname); +} + +void SitesController::removeSite(int index) +{ + auto modelIndex = m_sitesModel->index(index); + auto hostname = m_sitesModel->data(modelIndex, SitesModel::Roles::UrlRole).toString(); + m_sitesModel->removeSite(modelIndex); + + emit finished(tr("Site removed: ") + hostname); +} + +void SitesController::importSites(bool replaceExisting) +{ + QString fileName = Utils::getFileName(Q_NULLPTR, tr("Open sites file"), + QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.json"); + + if (fileName.isEmpty()) { + return; + } + + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) { + emit errorOccurred(tr("Can't open file: ") + fileName); + return; + } + + QByteArray jsonData = file.readAll(); + QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonData); + if (jsonDocument.isNull()) { + emit errorOccurred(tr("Failed to parse JSON data from file: ") + fileName); + return; + } + + if (!jsonDocument.isArray()) { + emit errorOccurred(tr("The JSON data is not an array in file: ") + fileName); + return; + } + + auto jsonArray = jsonDocument.array(); + QMap sites; + QStringList ips; + + for (auto jsonValue : jsonArray) { + auto jsonObject = jsonValue.toObject(); + auto hostname = jsonObject.value("hostname").toString(""); + auto ip = jsonObject.value("ip").toString(""); + + if (!hostname.contains(".") && !Utils::ipAddressWithSubnetRegExp().exactMatch(hostname)) { + qDebug() << hostname << " not look like ip adress or domain name"; + continue; + } + + if (ip.isEmpty()) { + ips.append(hostname); + } else { + ips.append(ip); + } + sites.insert(hostname, ip); + } + + m_sitesModel->addSites(sites, replaceExisting); + + m_vpnConnection->addRoutes(QStringList() << ips); + m_vpnConnection->flushDns(); + + emit finished(tr("Import completed")); +} + +void SitesController::exportSites() +{ + auto sites = m_sitesModel->getCurrentSites(); + + QJsonArray jsonArray; + + for (const auto &site : sites) { + QJsonObject jsonObject { { "hostname", site.first }, { "ip", site.second } }; + jsonArray.append(jsonObject); + } + + QJsonDocument jsonDocument(jsonArray); + QByteArray jsonData = jsonDocument.toJson(); + + Utils::saveFile(".json", tr("Export sites file"), "sites", jsonData); + + emit finished(tr("Export completed")); +} diff --git a/client/ui/controllers/sitesController.h b/client/ui/controllers/sitesController.h new file mode 100644 index 00000000..ff78c3de --- /dev/null +++ b/client/ui/controllers/sitesController.h @@ -0,0 +1,36 @@ +#ifndef SITESCONTROLLER_H +#define SITESCONTROLLER_H + +#include + +#include "settings.h" +#include "ui/models/sites_model.h" +#include "vpnconnection.h" + +class SitesController : public QObject +{ + Q_OBJECT +public: + explicit SitesController(const std::shared_ptr &settings, + const QSharedPointer &vpnConnection, + const QSharedPointer &sitesModel, QObject *parent = nullptr); + +public slots: + void addSite(QString hostname); + void removeSite(int index); + + void importSites(bool replaceExisting); + void exportSites(); + +signals: + void errorOccurred(const QString &errorMessage); + void finished(const QString &message); + +private: + std::shared_ptr m_settings; + + QSharedPointer m_vpnConnection; + QSharedPointer m_sitesModel; +}; + +#endif // SITESCONTROLLER_H diff --git a/client/ui/models/sites_model.cpp b/client/ui/models/sites_model.cpp index fe0f4ccf..5fd9a38b 100644 --- a/client/ui/models/sites_model.cpp +++ b/client/ui/models/sites_model.cpp @@ -1,87 +1,118 @@ #include "sites_model.h" -SitesModel::SitesModel(std::shared_ptr settings, Settings::RouteMode mode, QObject *parent) - : QAbstractListModel(parent), - m_settings(settings), - m_mode(mode) +SitesModel::SitesModel(std::shared_ptr settings, QObject *parent) + : QAbstractListModel(parent), m_settings(settings) { -} - -void SitesModel::resetCache() -{ - beginResetModel(); - m_ipsCache.clear(); - m_cacheReady = false; - endResetModel(); + m_currentRouteMode = m_settings->routeMode(); + fillSites(); } int SitesModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent) - if (!m_cacheReady) genCache(); - return m_ipsCache.size(); + return m_sites.size(); } - QVariant SitesModel::data(const QModelIndex &index, int role) const { - if (!index.isValid()) + if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(rowCount())) return QVariant(); - if (!m_cacheReady) genCache(); - - if (role == SitesModel::UrlRole || role == SitesModel::IpRole) { - if (m_ipsCache.isEmpty()) return QVariant(); - - if (role == SitesModel::UrlRole) { - return m_ipsCache.at(index.row()).first; - } - if (role == SitesModel::IpRole) { - return m_ipsCache.at(index.row()).second; - } + switch (role) { + case UrlRole: { + return m_sites.at(index.row()).first; + break; + } + case IpRole: { + return m_sites.at(index.row()).second; + break; + } + default: { + return true; + } } - - // if (role == Qt::TextAlignmentRole && index.column() == 1) { - // return Qt::AlignRight; - // } return QVariant(); } -QVariant SitesModel::data(int row, int column) +bool SitesModel::addSite(const QString &hostname, const QString &ip) { - if (row < 0 || row >= rowCount() || column < 0 || column >= 2) { - return QVariant(); + if (!m_settings->addVpnSite(m_currentRouteMode, hostname, ip)) { + return false; } - if (!m_cacheReady) genCache(); - - if (column == 0) { - return m_ipsCache.at(row).first; + for (int i = 0; i < m_sites.size(); i++) { + if (m_sites[i].first == hostname && (m_sites[i].second.isEmpty() && !ip.isEmpty())) { + m_sites[i].second = ip; + QModelIndex index = createIndex(i, i); + emit dataChanged(index, index); + return true; + } else if (m_sites[i].first == hostname && (m_sites[i].second == ip)) { + return false; + } } - if (column == 1) { - return m_ipsCache.at(row).second; - } - return QVariant(); + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + m_sites.append(qMakePair(hostname, ip)); + endInsertRows(); + return true; } -void SitesModel::genCache() const +void SitesModel::addSites(const QMap &sites, bool replaceExisting) { - qDebug() << "SitesModel::genCache"; - m_ipsCache.clear(); + beginResetModel(); - const QVariantMap &sites = m_settings->vpnSites(m_mode); - auto i = sites.constBegin(); - while (i != sites.constEnd()) { - m_ipsCache.append(qMakePair(i.key(), i.value().toString())); - ++i; + if (replaceExisting) { + m_settings->removeAllVpnSites(m_currentRouteMode); } + m_settings->addVpnSites(m_currentRouteMode, sites); + fillSites(); - m_cacheReady= true; + endResetModel(); } -QHash SitesModel::roleNames() const { +void SitesModel::removeSite(QModelIndex index) +{ + auto hostname = m_sites.at(index.row()).first; + beginRemoveRows(QModelIndex(), index.row(), index.row()); + m_settings->removeVpnSite(m_currentRouteMode, hostname); + m_sites.removeAt(index.row()); + endRemoveRows(); +} + +int SitesModel::getRouteMode() +{ + return m_currentRouteMode; +} + +void SitesModel::setRouteMode(int routeMode) +{ + beginResetModel(); + m_settings->setRouteMode(static_cast(routeMode)); + m_currentRouteMode = m_settings->routeMode(); + fillSites(); + endResetModel(); + emit routeModeChanged(); +} + +QVector > SitesModel::getCurrentSites() +{ + return m_sites; +} + +QHash SitesModel::roleNames() const +{ QHash roles; - roles[UrlRole] = "url_path"; + roles[UrlRole] = "url"; roles[IpRole] = "ip"; return roles; } + +void SitesModel::fillSites() +{ + m_sites.clear(); + const QVariantMap &sites = m_settings->vpnSites(m_currentRouteMode); + auto i = sites.constBegin(); + while (i != sites.constEnd()) { + m_sites.append(qMakePair(i.key(), i.value().toString())); + ++i; + } +} diff --git a/client/ui/models/sites_model.h b/client/ui/models/sites_model.h index 7bf04b50..70def0ec 100644 --- a/client/ui/models/sites_model.h +++ b/client/ui/models/sites_model.h @@ -10,32 +10,43 @@ class SitesModel : public QAbstractListModel Q_OBJECT public: - enum SiteRoles { + enum Roles { UrlRole = Qt::UserRole + 1, IpRole }; - explicit SitesModel(std::shared_ptr settings, Settings::RouteMode mode, QObject *parent = nullptr); - void resetCache(); + explicit SitesModel(std::shared_ptr settings, QObject *parent = nullptr); - // Basic functionality: int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - QVariant data(int row, int column); + + Q_PROPERTY(int routeMode READ getRouteMode WRITE setRouteMode NOTIFY routeModeChanged) + +public slots: + bool addSite(const QString &hostname, const QString &ip); + void addSites(const QMap &sites, bool replaceExisting); + void removeSite(QModelIndex index); + + int getRouteMode(); + void setRouteMode(int routeMode); + + QVector> getCurrentSites(); + +signals: + void routeModeChanged(); protected: QHash roleNames() const override; private: - void genCache() const; + void fillSites(); -private: - Settings::RouteMode m_mode; std::shared_ptr m_settings; - mutable QVector> m_ipsCache; - mutable bool m_cacheReady = false; + Settings::RouteMode m_currentRouteMode; + + QVector> m_sites; }; #endif // SITESMODEL_H diff --git a/client/ui/pages_logic/SitesLogic.cpp b/client/ui/pages_logic/SitesLogic.cpp index bf926e56..7f653bfa 100644 --- a/client/ui/pages_logic/SitesLogic.cpp +++ b/client/ui/pages_logic/SitesLogic.cpp @@ -9,23 +9,24 @@ #include "vpnconnection.h" #include -#include "../uilogic.h" #include "../models/sites_model.h" +#include "../uilogic.h" -SitesLogic::SitesLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent), - m_labelSitesAddCustomText{}, - m_tableViewSitesModel{nullptr}, - m_lineEditSitesAddCustomText{} +SitesLogic::SitesLogic(UiLogic *logic, QObject *parent) + : PageLogicBase(logic, parent), + m_labelSitesAddCustomText {}, + m_tableViewSitesModel { nullptr }, + m_lineEditSitesAddCustomText {} { - sitesModels.insert(Settings::VpnOnlyForwardSites, new SitesModel(m_settings, Settings::VpnOnlyForwardSites)); - sitesModels.insert(Settings::VpnAllExceptSites, new SitesModel(m_settings, Settings::VpnAllExceptSites)); + // sitesModels.insert(Settings::VpnOnlyForwardSites, new SitesModel(m_settings, Settings::VpnOnlyForwardSites)); + // sitesModels.insert(Settings::VpnAllExceptSites, new SitesModel(m_settings, Settings::VpnAllExceptSites)); } void SitesLogic::onUpdatePage() { Settings::RouteMode m = m_settings->routeMode(); - if (m == Settings::VpnAllSites) return; + if (m == Settings::VpnAllSites) + return; if (m == Settings::VpnOnlyForwardSites) { set_labelSitesAddCustomText(tr("These sites will be opened using VPN")); @@ -35,7 +36,7 @@ void SitesLogic::onUpdatePage() } set_tableViewSitesModel(sitesModels.value(m)); - sitesModels.value(m)->resetCache(); + // sitesModels.value(m)->resetCache(); } void SitesLogic::onPushButtonAddCustomSitesClicked() @@ -47,8 +48,10 @@ void SitesLogic::onPushButtonAddCustomSitesClicked() QString newSite = lineEditSitesAddCustomText(); - if (newSite.isEmpty()) return; - if (!newSite.contains(".")) return; + if (newSite.isEmpty()) + return; + if (!newSite.contains(".")) + return; if (!Utils::ipAddressWithSubnetRegExp().exactMatch(newSite)) { // get domain name if it present @@ -65,8 +68,7 @@ void SitesLogic::onPushButtonAddCustomSitesClicked() if (!ip.isEmpty()) { uiLogic()->m_vpnConnection->addRoutes(QStringList() << ip); uiLogic()->m_vpnConnection->flushDns(); - } - else if (Utils::ipAddressWithSubnetRegExp().exactMatch(newSite)) { + } else if (Utils::ipAddressWithSubnetRegExp().exactMatch(newSite)) { uiLogic()->m_vpnConnection->addRoutes(QStringList() << newSite); uiLogic()->m_vpnConnection->flushDns(); } @@ -74,10 +76,10 @@ void SitesLogic::onPushButtonAddCustomSitesClicked() onUpdatePage(); }; - const auto &cbResolv = [this, cbProcess](const QHostInfo &hostInfo){ + const auto &cbResolv = [this, cbProcess](const QHostInfo &hostInfo) { const QList &addresses = hostInfo.addresses(); QString ipv4Addr; - for (const QHostAddress &addr: hostInfo.addresses()) { + for (const QHostAddress &addr : hostInfo.addresses()) { if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) { cbProcess(hostInfo.hostName(), addr.toString()); break; @@ -90,8 +92,7 @@ void SitesLogic::onPushButtonAddCustomSitesClicked() if (Utils::ipAddressWithSubnetRegExp().exactMatch(newSite)) { cbProcess(newSite, ""); return; - } - else { + } else { cbProcess(newSite, ""); onUpdatePage(); QHostInfo::lookupHost(newSite, this, cbResolv); @@ -102,7 +103,7 @@ void SitesLogic::onPushButtonSitesDeleteClicked(QStringList items) { Settings::RouteMode mode = m_settings->routeMode(); - auto siteModel = qobject_cast (tableViewSitesModel()); + auto siteModel = qobject_cast(tableViewSitesModel()); if (!siteModel || items.isEmpty()) { return; } @@ -110,14 +111,15 @@ void SitesLogic::onPushButtonSitesDeleteClicked(QStringList items) QStringList sites; QStringList ips; - for (const QString &s: items) { + for (const QString &s : items) { bool ok; int row = s.toInt(&ok); - if (!ok || row < 0 || row >= siteModel->rowCount()) return; - sites.append(siteModel->data(row, 0).toString()); + if (!ok || row < 0 || row >= siteModel->rowCount()) + return; + // sites.append(siteModel->data(row, 0).toString()); if (uiLogic()->m_vpnConnection->connectionState() == Vpn::ConnectionState::Connected) { - ips.append(siteModel->data(row, 1).toString()); + // ips.append(siteModel->data(row, 1).toString()); } } @@ -131,11 +133,11 @@ void SitesLogic::onPushButtonSitesDeleteClicked(QStringList items) onUpdatePage(); } -void SitesLogic::onPushButtonSitesImportClicked(const QString& fileName) +void SitesLogic::onPushButtonSitesImportClicked(const QString &fileName) { - QFile file(QUrl{fileName}.toLocalFile()); + QFile file(QUrl { fileName }.toLocalFile()); if (!file.open(QIODevice::ReadOnly)) { - qDebug() << "Can't open file " << QUrl{fileName}.toLocalFile(); + qDebug() << "Can't open file " << QUrl { fileName }.toLocalFile(); return; } @@ -164,27 +166,24 @@ void SitesLogic::onPushButtonSitesImportClicked(const QString& fileName) } // domain regex cover ip regex, so remove ips from sites - for (const QString& ip: line_ips) { + for (const QString &ip : line_ips) { line_sites.removeAll(ip); } if (line_sites.size() == 1 && line_ips.size() == 1) { sites.insert(line_sites.at(0), line_ips.at(0)); - } - else if (line_sites.size() > 0 && line_ips.size() == 0) { - for (const QString& site: line_sites) { + } else if (line_sites.size() > 0 && line_ips.size() == 0) { + for (const QString &site : line_sites) { sites.insert(site, ""); } - } - else { - for (const QString& site: line_sites) { + } else { + for (const QString &site : line_sites) { sites.insert(site, ""); } - for (const QString& ip: line_ips) { + for (const QString &ip : line_ips) { ips.append(ip); } } - } m_settings->addVpnIps(mode, ips); @@ -208,4 +207,3 @@ void SitesLogic::onPushButtonSitesExportClicked() } uiLogic()->saveTextFile("Export Sites", "sites.txt", ".txt", data); } - diff --git a/client/ui/qml/Controls2/BasicButtonType.qml b/client/ui/qml/Controls2/BasicButtonType.qml index c69d51d7..b0c39ddc 100644 --- a/client/ui/qml/Controls2/BasicButtonType.qml +++ b/client/ui/qml/Controls2/BasicButtonType.qml @@ -20,6 +20,8 @@ Button { property string imageSource + property bool squareLeftSide: false + implicitHeight: 56 hoverEnabled: true @@ -44,6 +46,32 @@ Button { Behavior on color { PropertyAnimation { duration: 200 } } + + Rectangle { + visible: root.squareLeftSide + + z: 1 + + width: parent.radius + height: parent.radius + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + color: { + if (root.enabled) { + if (root.pressed) { + return pressedColor + } + return root.hovered ? hoveredColor : defaultColor + } else { + return disabledColor + } + } + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } } MouseArea { diff --git a/client/ui/qml/Controls2/ContextMenuType.qml b/client/ui/qml/Controls2/ContextMenuType.qml new file mode 100644 index 00000000..867fcb10 --- /dev/null +++ b/client/ui/qml/Controls2/ContextMenuType.qml @@ -0,0 +1,33 @@ +import QtQuick +import QtQuick.Controls +import Qt.labs.platform + +Menu { + property var textObj + + MenuItem { + text: qsTr("C&ut") + shortcut: StandardKey.Cut + enabled: textObj.selectedText + onTriggered: textObj.cut() + } + MenuItem { + text: qsTr("&Copy") + shortcut: StandardKey.Copy + enabled: textObj.selectedText + onTriggered: textObj.copy() + } + MenuItem { + text: qsTr("&Paste") + shortcut: StandardKey.Paste + enabled: textObj.canPaste + onTriggered: textObj.paste() + } + + MenuItem { + text: qsTr("&SelectAll") + shortcut: StandardKey.SelectAll + enabled: textObj.length > 0 + onTriggered: textObj.selectAll() + } +} diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 7a4538ba..17151630 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -24,9 +24,11 @@ Item { property string rootButtonBackgroundColor: "#1C1D21" property string rootButtonHoveredBorderColor: "#494B50" - property string rootButtonDefaultBorderColor: "transparent" + property string rootButtonDefaultBorderColor: "#2C2D30" property string rootButtonPressedBorderColor: "#D7D8DB" + property int rootButtonTextMargins: 16 + property real drawerHeight: 0.9 property Component listView @@ -74,7 +76,9 @@ Item { spacing: 0 ColumnLayout { - Layout.leftMargin: 16 + Layout.leftMargin: rootButtonTextMargins + Layout.topMargin: rootButtonTextMargins + Layout.bottomMargin: rootButtonTextMargins LabelTextType { Layout.fillWidth: true @@ -96,16 +100,10 @@ Item { color: root.enabled ? root.textColor : root.textDisabledColor text: root.text - - wrapMode: Text.NoWrap - elide: Text.ElideRight } } ImageButtonType { - Layout.leftMargin: 4 - Layout.rightMargin: 16 - hoverEnabled: false image: rootButtonImage imageColor: rootButtonImageColor diff --git a/client/ui/qml/Controls2/TextAreaType.qml b/client/ui/qml/Controls2/TextAreaType.qml new file mode 100644 index 00000000..9958f2e9 --- /dev/null +++ b/client/ui/qml/Controls2/TextAreaType.qml @@ -0,0 +1,70 @@ +import QtQuick +import QtQuick.Controls + +Rectangle { + id: root + + property string placeholderText + property string text + property var onEditingFinished + + height: 148 + color: "#1C1D21" + border.width: 1 + border.color: "#2C2D30" + radius: 16 + + FlickableType { + anchors.top: parent.top + anchors.bottom: parent.bottom + contentHeight: textArea.implicitHeight + TextArea { + id: textArea + + width: parent.width + + topPadding: 16 + leftPadding: 16 + anchors.topMargin: 16 + anchors.bottomMargin: 16 + + color: "#D7D8DB" + selectionColor: "#412102" + selectedTextColor: "#D7D8DB" + placeholderTextColor: "#878B91" + + font.pixelSize: 16 + font.weight: Font.Medium + font.family: "PT Root UI VF" + + placeholderText: root.placeholderText + text: root.text + + onEditingFinished: { + if (root.onEditingFinished && typeof root.onEditingFinished === "function") { + root.onEditingFinished() + } + } + + wrapMode: Text.Wrap + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton + onClicked: contextMenu.open() + } + + ContextMenuType { + id: contextMenu + textObj: textArea + } + } + } + + //todo make whole background clickable, with code below we lose ability to select text by mouse +// MouseArea { +// anchors.fill: parent +// cursorShape: Qt.IBeamCursor +// onClicked: textArea.forceActiveFocus() +// } +} diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml index 1414b5ec..3f31c224 100644 --- a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -41,7 +41,7 @@ Item { Rectangle { id: backgroud Layout.fillWidth: true - Layout.preferredHeight: 74 + Layout.preferredHeight: input.implicitHeight color: root.enabled ? root.backgroundColor : root.backgroundDisabledColor radius: 16 border.color: textField.focus ? root.borderFocusedColor : root.borderColor @@ -52,16 +52,17 @@ Item { } RowLayout { + id: input anchors.fill: backgroud ColumnLayout { + Layout.margins: 16 LabelTextType { text: root.headerText color: root.enabled ? root.headerTextColor : root.headerTextDisabledColor + visible: text !== "" + Layout.fillWidth: true - Layout.rightMargin: 16 - Layout.leftMargin: 16 - Layout.topMargin: 16 } TextField { @@ -82,9 +83,7 @@ Item { height: 24 Layout.fillWidth: true - Layout.rightMargin: 16 - Layout.leftMargin: 16 - Layout.bottomMargin: 16 + topPadding: 0 rightPadding: 0 leftPadding: 0 @@ -98,24 +97,37 @@ Item { onTextChanged: { root.errorText = "" } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton + onClicked: contextMenu.open() + } + + ContextMenuType { + id: contextMenu + textObj: textField + } } } BasicButtonType { visible: (root.buttonText !== "") || (root.buttonImageSource !== "") - defaultColor: "transparent" - hoveredColor: Qt.rgba(1, 1, 1, 0.08) - pressedColor: Qt.rgba(1, 1, 1, 0.12) - disabledColor: "#878B91" - textColor: "#D7D8DB" - borderWidth: 0 +// defaultColor: "transparent" +// hoveredColor: Qt.rgba(1, 1, 1, 0.08) +// pressedColor: Qt.rgba(1, 1, 1, 0.12) +// disabledColor: "#878B91" +// textColor: "#D7D8DB" +// borderWidth: 0 text: root.buttonText imageSource: root.buttonImageSource - Layout.rightMargin: 24 - Layout.preferredHeight: 32 +// Layout.rightMargin: 24 + Layout.preferredHeight: content.implicitHeight + Layout.preferredWidth: content.implicitHeight + squareLeftSide: true onClicked: { if (root.clickedFunc && typeof root.clickedFunc === "function") { diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index ce5ddcd4..b756511b 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -148,10 +148,11 @@ PageType { DropDownType { id: containersDropDown - implicitHeight: 40 - rootButtonImageColor: "#0E0E11" rootButtonBackgroundColor: "#D7D8DB" + rootButtonHoveredBorderColor: "transparent" + rootButtonPressedBorderColor: "transparent" + rootButtonTextMargins: 8 text: root.defaultContainerName textColor: "#0E0E11" diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index 7d38a2b0..d25fb659 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -301,50 +301,18 @@ PageType { text: qsTr("Additional client configuration commands") } - Rectangle { + TextAreaType { Layout.fillWidth: true Layout.topMargin: 16 - height: 148 - color: "#1C1D21" - border.width: 1 - border.color: "#2C2D30" - radius: 16 visible: additionalClientCommandsSwitcher.checked - FlickableType { - anchors.top: parent.top - anchors.bottom: parent.bottom - contentHeight: additionalClientCommandsTextArea.implicitHeight - TextArea { - id: additionalClientCommandsTextArea + text: additionalClientCommands + placeholderText: qsTr("Commands:") - width: parent.width - anchors.topMargin: 16 - anchors.bottomMargin: 16 - - topPadding: 16 - leftPadding: 16 - - color: "#D7D8DB" - selectionColor: "#412102" - selectedTextColor: "#D7D8DB" - placeholderTextColor: "#878B91" - - font.pixelSize: 16 - font.weight: Font.Medium - font.family: "PT Root UI VF" - - placeholderText: qsTr("Commands:") - text: additionalClientCommands - - wrapMode: Text.Wrap - - onEditingFinished: { - if (additionalClientCommands !== text) { - additionalClientCommands = text - } - } + onEditingFinished: { + if (additionalClientCommands !== text) { + additionalClientCommands = text } } } @@ -359,50 +327,18 @@ PageType { text: qsTr("Additional server configuration commands") } - Rectangle { + TextAreaType { Layout.fillWidth: true Layout.topMargin: 16 - height: 148 - color: "#1C1D21" - border.width: 1 - border.color: "#2C2D30" - radius: 16 visible: additionalServerCommandsSwitcher.checked - FlickableType { - anchors.top: parent.top - anchors.bottom: parent.bottom - contentHeight: additionalServerCommandsTextArea.implicitHeight - TextArea { - id: additionalServerCommandsTextArea + text: additionalServerCommands + placeholderText: qsTr("Commands:") - width: parent.width - anchors.topMargin: 16 - anchors.bottomMargin: 16 - - topPadding: 16 - leftPadding: 16 - - color: "#D7D8DB" - selectionColor: "#412102" - selectedTextColor: "#D7D8DB" - placeholderTextColor: "#878B91" - - font.pixelSize: 16 - font.weight: Font.Medium - font.family: "PT Root UI VF" - - placeholderText: qsTr("Commands:") - text: additionalServerCommands - - wrapMode: Text.Wrap - - onEditingFinished: { - if (additionalServerCommands !== text) { - additionalServerCommands = text - } - } + onEditingFinished: { + if (additionalServerCommands !== text) { + additionalServerCommands = text } } } diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index 2001e892..9f2fe91a 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -67,7 +67,7 @@ PageType { Layout.fillWidth: true text: qsTr("Logging") - descriptionText: SettingsController.isLoggingEnable ? qsTr("Enabled") : qsTr("Disabled") + descriptionText: SettingsController.isLoggingEnabled ? qsTr("Enabled") : qsTr("Disabled") rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml index fd365edb..6465e40e 100644 --- a/client/ui/qml/Pages2/PageSettingsConnection.qml +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -41,6 +41,24 @@ PageType { headerText: qsTr("Connection") } + SwitcherType { + Layout.fillWidth: true + Layout.topMargin: 16 + Layout.bottomMargin: 16 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + text: qsTr("Auto connect") + descriptionText: qsTr("Connect to VPN on app start") + + checked: SettingsController.isAutoConnectEnabled() + onCheckedChanged: { + if (checked !== SettingsController.isAutoConnectEnabled()) { + SettingsController.toggleAutoConnect(checked) + } + } + } + SwitcherType { Layout.fillWidth: true Layout.topMargin: 16 @@ -54,7 +72,7 @@ PageType { checked: SettingsController.isAmneziaDnsEnabled() onCheckedChanged: { if (checked !== SettingsController.isAmneziaDnsEnabled()) { - SettingsController.setAmneziaDns(checked) + SettingsController.toggleAmneziaDns(checked) } } } @@ -83,6 +101,7 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { + goToPage(PageEnum.PageSettingsSplitTunneling) } } diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml index 998065e4..5138f9a3 100644 --- a/client/ui/qml/Pages2/PageSettingsLogging.qml +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -50,10 +50,10 @@ PageType { text: qsTr("Save logs") - checked: SettingsController.isLoggingEnable + checked: SettingsController.isLoggingEnabled onCheckedChanged: { - if (checked !== SettingsController.isLoggingEnable) { - SettingsController.isLoggingEnable = checked + if (checked !== SettingsController.isLoggingEnabled) { + SettingsController.isLoggingEnabled = checked } } } diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml new file mode 100644 index 00000000..a43e9e5e --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -0,0 +1,377 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 +import ContainerProps 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + Connections { + target: SitesController + + function onFinished(message) { + PageController.showNotificationMessage(message) + } + + function onErrorOccurred(errorMessage) { + PageController.showErrorMessage(errorMessage) + } + } + + QtObject { + id: routeMode + property int allSites: 0 + property int onlyForwardSites: 1 + property int allExceptSites: 2 + } + + property list routeModesModel: [ + onlyForwardSites, + allExceptSites + ] + + QtObject { + id: onlyForwardSites + property string name: qsTr("Addresses from the list should always open via VPN") + property int type: routeMode.onlyForwardSites + } + QtObject { + id: allExceptSites + property string name: qsTr("Addresses from the list should never be opened via VPN") + property int type: routeMode.allExceptSites + } + + function getRouteModesModelIndex() { + var currentRouteMode = SitesModel.routeMode + if ((routeMode.onlyForwardSites === currentRouteMode) || (routeMode.allSites === currentRouteMode)) { + return 0 + } else if (routeMode.allExceptSites === currentRouteMode) { + return 1 + } + } + + ColumnLayout { + id: header + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + } + + RowLayout { + HeaderType { + Layout.fillWidth: true + Layout.leftMargin: 16 + + headerText: qsTr("Split site tunneling") + } + + SwitcherType { + id: switcher + + property int lastActiveRouteMode: routeMode.onlyForwardSites + + Layout.fillWidth: true + Layout.rightMargin: 16 + + checked: SitesModel.routeMode !== routeMode.allSites + onToggled: { + if (checked) { + SitesModel.routeMode = lastActiveRouteMode + } else { + lastActiveRouteMode = SitesModel.routeMode + selector.text = root.routeModesModel[getRouteModesModelIndex()].name + SitesModel.routeMode = routeMode.allSites + } + } + } + } + + DropDownType { + id: selector + + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + drawerHeight: 0.4375 + + enabled: switcher.checked + + headerText: qsTr("Mode") + + listView: ListViewType { + rootWidth: root.width + + model: root.routeModesModel + + currentIndex: getRouteModesModelIndex() + + clickedFunction: function() { + selector.text = selectedText + selector.menuVisible = false + if (SitesModel.routeMode !== root.routeModesModel[currentIndex].type) { + SitesModel.routeMode = root.routeModesModel[currentIndex].type + } + } + + Component.onCompleted: { + if (root.routeModesModel[currentIndex].type === SitesModel.routeMode) { + selector.text = selectedText + } else { + selector.text = root.routeModesModel[0].name + } + } + + Connections { + target: SitesModel + function onRouteModeChanged() { + currentIndex = getRouteModesModelIndex() + } + } + } + } + } + + FlickableType { + anchors.top: header.bottom + anchors.topMargin: 16 + contentHeight: col.implicitHeight + connectButton.implicitHeight + connectButton.anchors.bottomMargin + connectButton.anchors.topMargin + + enabled: switcher.checked + + Column { + id: col + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + ListView { + id: sites + width: parent.width + height: sites.contentItem.height + + model: SitesModel + + clip: true + interactive: false + + delegate: Item { + implicitWidth: sites.width + implicitHeight: delegateContent.implicitHeight + + ColumnLayout { + id: delegateContent + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + LabelWithButtonType { + Layout.fillWidth: true + + text: url + descriptionText: ip + rightImageSource: "qrc:/images/controls/trash.svg" + rightImageColor: "#D7D8DB" + + clickedFunction: function() { + questionDrawer.headerText = qsTr("Remove ") + url + "?" + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + SitesController.removeSite(index) + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true + } + } + + DividerType {} + + QuestionDrawer { + id: questionDrawer + } + } + } + } + } + } + + RowLayout { + id: connectButton + + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 24 + anchors.rightMargin: 16 + anchors.leftMargin: 16 + anchors.bottomMargin: 24 + + TextFieldWithHeaderType { + Layout.fillWidth: true + + textFieldPlaceholderText: qsTr("Site or IP") + buttonImageSource: "qrc:/images/controls/plus.svg" + + clickedFunc: function() { + SitesController.addSite(textFieldText) + textFieldText = "" + } + } + + ImageButtonType { + implicitWidth: 56 + implicitHeight: 56 + + image: "qrc:/images/controls/more-vertical.svg" + imageColor: "#D7D8DB" + + onClicked: function () { + moreActionsDrawer.open() + } + } + } + + DrawerType { + id: moreActionsDrawer + + width: parent.width + height: parent.height * 0.4375 + + FlickableType { + anchors.fill: parent + contentHeight: moreActionsDrawerContent.height + ColumnLayout { + id: moreActionsDrawerContent + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + Header2Type { + Layout.fillWidth: true + Layout.margins: 16 + + headerText: qsTr("Import/Export Sites") + } + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Import") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + importSitesDrawer.open() + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + text: qsTr("Save site list") + + clickedFunction: function() { + SitesController.exportSites() + moreActionsDrawer.close() + } + } + + DividerType {} + } + } + } + + DrawerType { + id: importSitesDrawer + + width: parent.width + height: parent.height * 0.4375 + + BackButtonType { + id: importSitesDrawerBackButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 + + backButtonFunction: function() { + importSitesDrawer.close() + } + } + + FlickableType { + anchors.top: importSitesDrawerBackButton.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + + contentHeight: importSitesDrawerContent.height + ColumnLayout { + id: importSitesDrawerContent + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + Header2Type { + Layout.fillWidth: true + Layout.margins: 16 + + headerText: qsTr("Import a list of sites") + } + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Replace site list") + + clickedFunction: function() { + SitesController.importSites(true) + importSitesDrawer.close() + moreActionsDrawer.close() + } + } + + DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + text: qsTr("Add imported sites to existing ones") + + clickedFunction: function() { + SitesController.importSites(false) + importSitesDrawer.close() + moreActionsDrawer.close() + } + } + + DividerType {} + } + } + } +} From 0c40a954fad64efc89092490601ef68698c3a450 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 8 Aug 2023 19:39:08 +0500 Subject: [PATCH 057/278] fixed include in sitesController for android build --- client/ui/controllers/sitesController.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ui/controllers/sitesController.cpp b/client/ui/controllers/sitesController.cpp index 7f3f93e1..98790630 100644 --- a/client/ui/controllers/sitesController.cpp +++ b/client/ui/controllers/sitesController.cpp @@ -1,6 +1,7 @@ #include "sitesController.h" #include +#include #include "utilities.h" From 591d98d8b60aef386141ad6dc67559764fd64e02 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 9 Aug 2023 18:17:29 +0500 Subject: [PATCH 058/278] moved vpnConnection to separate thread - added tabbar blocking when installing/removing containers --- client/amnezia_application.cpp | 23 ++++ client/amnezia_application.h | 1 + client/main.cpp | 19 ++-- client/protocols/openvpnprotocol.cpp | 103 +++++++++--------- client/ui/controllers/pageController.h | 1 + client/ui/controllers/sitesController.cpp | 17 ++- client/ui/pages_logic/SitesLogic.cpp | 32 ++---- client/ui/qml/Pages2/PageDeinstalling.qml | 3 + .../Pages2/PageProtocolOpenVpnSettings.qml | 6 + client/ui/qml/Pages2/PageSettingsBackup.qml | 2 +- .../ui/qml/Pages2/PageSettingsServerData.qml | 2 +- .../qml/Pages2/PageSetupWizardInstalling.qml | 3 + client/ui/qml/Pages2/PageStart.qml | 8 +- client/ui/uilogic.cpp | 3 - 14 files changed, 128 insertions(+), 95 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 933a6346..20b5ac3d 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -57,6 +57,27 @@ AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecond AmneziaApplication::~AmneziaApplication() { + // emit hide(); + + // #ifdef AMNEZIA_DESKTOP + // if (m_vpnConnection->connectionState() != Vpn::ConnectionState::Disconnected) { + // m_vpnConnection->disconnectFromVpn(); + // for (int i = 0; i < 50; i++) { + // qApp->processEvents(QEventLoop::ExcludeUserInputEvents); + // QThread::msleep(100); + // if (m_vpnConnection->isDisconnected()) { + // break; + // } + // } + // } + // #endif + + // m_vpnConnection->deleteLater(); + // m_vpnConnectionThread.quit(); + // m_vpnConnectionThread.wait(3000); + + // qDebug() << "Application closed"; + if (m_engine) { QObject::disconnect(m_engine, 0, 0, 0); delete m_engine; @@ -87,6 +108,8 @@ void AmneziaApplication::init() m_configurator = std::shared_ptr(new VpnConfigurator(m_settings, this)); m_vpnConnection.reset(new VpnConnection(m_settings, m_configurator)); + m_vpnConnection->moveToThread(&m_vpnConnectionThread); + m_vpnConnectionThread.start(); initModels(); initControllers(); diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 81897c83..675ffd81 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -101,6 +101,7 @@ private: QScopedPointer m_sftpConfigModel; QSharedPointer m_vpnConnection; + QThread m_vpnConnectionThread; QScopedPointer m_notificationHandler; QScopedPointer m_connectionController; diff --git a/client/main.cpp b/client/main.cpp index 72ed6018..e78a74ff 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -2,20 +2,19 @@ #include #include "amnezia_application.h" -#include "version.h" #include "migrations.h" +#include "version.h" #include #ifdef Q_OS_WIN -#include "Windows.h" + #include "Windows.h" #endif #if defined(Q_OS_IOS) -#include "platforms/ios/QtAppDelegate-C-Interface.h" + #include "platforms/ios/QtAppDelegate-C-Interface.h" #endif - int main(int argc, char *argv[]) { Migrations migrationsManager; @@ -27,16 +26,14 @@ int main(int argc, char *argv[]) AllowSetForegroundWindow(ASFW_ANY); #endif - #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) AmneziaApplication app(argc, argv); #else - AmneziaApplication app(argc, argv, true, SingleApplication::Mode::User | SingleApplication::Mode::SecondaryNotification); + AmneziaApplication app(argc, argv, true, + SingleApplication::Mode::User | SingleApplication::Mode::SecondaryNotification); if (!app.isPrimary()) { - QTimer::singleShot(1000, &app, [&](){ - app.quit(); - }); + QTimer::singleShot(1000, &app, [&]() { app.quit(); }); return app.exec(); } #endif @@ -63,6 +60,10 @@ int main(int argc, char *argv[]) if (doExec) { app.init(); + + qInfo().noquote() << QString("Started %1 version %2").arg(APPLICATION_NAME, APP_VERSION); + qInfo().noquote() << QString("%1 (%2)").arg(QSysInfo::prettyProductName(), QSysInfo::currentCpuArchitecture()); + return app.exec(); } return 0; diff --git a/client/protocols/openvpnprotocol.cpp b/client/protocols/openvpnprotocol.cpp index 7db64231..9551cb46 100644 --- a/client/protocols/openvpnprotocol.cpp +++ b/client/protocols/openvpnprotocol.cpp @@ -1,21 +1,20 @@ #include #include #include -#include -#include #include +#include +#include #include "logger.h" -#include "version.h" -#include "utilities.h" #include "openvpnprotocol.h" +#include "utilities.h" +#include "version.h" - -OpenVpnProtocol::OpenVpnProtocol(const QJsonObject &configuration, QObject* parent) : - VpnProtocol(configuration, parent) +OpenVpnProtocol::OpenVpnProtocol(const QJsonObject &configuration, QObject *parent) : VpnProtocol(configuration, parent) { readOpenVpnConfiguration(configuration); - connect(&m_managementServer, &ManagementServer::readyRead, this, &OpenVpnProtocol::onReadyReadDataFromManagementServer); + connect(&m_managementServer, &ManagementServer::readyRead, this, + &OpenVpnProtocol::onReadyReadDataFromManagementServer); } OpenVpnProtocol::~OpenVpnProtocol() @@ -27,7 +26,7 @@ OpenVpnProtocol::~OpenVpnProtocol() QString OpenVpnProtocol::defaultConfigFileName() { - //qDebug() << "OpenVpnProtocol::defaultConfigFileName" << defaultConfigPath() + QString("/%1.ovpn").arg(APPLICATION_NAME); + // qDebug() << "OpenVpnProtocol::defaultConfigFileName" << defaultConfigPath() + QString("/%1.ovpn").arg(APPLICATION_NAME); return defaultConfigPath() + QString("/%1.ovpn").arg(APPLICATION_NAME); } @@ -42,21 +41,20 @@ QString OpenVpnProtocol::defaultConfigPath() void OpenVpnProtocol::stop() { qDebug() << "OpenVpnProtocol::stop()"; - setConnectionState(VpnProtocol::Disconnecting); + setConnectionState(Vpn::ConnectionState::Disconnecting); // TODO: need refactoring // sendTermSignal() will even return true while server connected ??? - if ((m_connectionState == Vpn::ConnectionState::Preparing) || - (m_connectionState == Vpn::ConnectionState::Connecting) || - (m_connectionState == Vpn::ConnectionState::Connected) || - (m_connectionState == Vpn::ConnectionState::Reconnecting)) { + if ((m_connectionState == Vpn::ConnectionState::Preparing) || (m_connectionState == Vpn::ConnectionState::Connecting) + || (m_connectionState == Vpn::ConnectionState::Connected) + || (m_connectionState == Vpn::ConnectionState::Reconnecting)) { if (!sendTermSignal()) { killOpenVpnProcess(); } QThread::msleep(10); m_managementServer.stop(); } - setConnectionState(VpnProtocol::Disconnected); + setConnectionState(Vpn::ConnectionState::Disconnected); } ErrorCode OpenVpnProtocol::prepare() @@ -68,18 +66,19 @@ ErrorCode OpenVpnProtocol::prepare() QRemoteObjectPendingReply resultCheck = IpcClient::Interface()->getTapList(); resultCheck.waitForFinished(); - if (resultCheck.returnValue().isEmpty()){ + if (resultCheck.returnValue().isEmpty()) { QRemoteObjectPendingReply resultInstall = IpcClient::Interface()->checkAndInstallDriver(); resultInstall.waitForFinished(); - if (!resultInstall.returnValue()) return ErrorCode::OpenVpnTapAdapterError; + if (!resultInstall.returnValue()) + return ErrorCode::OpenVpnTapAdapterError; } return ErrorCode::NoError; } void OpenVpnProtocol::killOpenVpnProcess() { - if (m_openVpnProcess){ + if (m_openVpnProcess) { m_openVpnProcess->close(); } } @@ -113,9 +112,9 @@ QString OpenVpnProtocol::configPath() const return m_configFileName; } -void OpenVpnProtocol::sendManagementCommand(const QString& command) +void OpenVpnProtocol::sendManagementCommand(const QString &command) { - QIODevice *device = dynamic_cast(m_managementServer.socket().data()); + QIODevice *device = dynamic_cast(m_managementServer.socket().data()); if (device) { QTextStream stream(device); stream << command << Qt::endl; @@ -127,11 +126,12 @@ uint OpenVpnProtocol::selectMgmtPort() for (int i = 0; i < 100; ++i) { quint32 port = QRandomGenerator::global()->generate(); - port = (double)(65000-15001) * port / UINT32_MAX + 15001; + port = (double)(65000 - 15001) * port / UINT32_MAX + 15001; QTcpServer s; bool ok = s.listen(QHostAddress::LocalHost, port); - if (ok) return port; + if (ok) + return port; } return m_managementPort; @@ -141,7 +141,8 @@ void OpenVpnProtocol::updateRouteGateway(QString line) { // TODO: fix for macos line = line.split("ROUTE_GATEWAY", Qt::SkipEmptyParts).at(1); - if (!line.contains("/")) return; + if (!line.contains("/")) + return; m_routeGateway = line.split("/", Qt::SkipEmptyParts).first(); m_routeGateway.replace(" ", ""); qDebug() << "Set VPN route gateway" << m_routeGateway; @@ -149,7 +150,7 @@ void OpenVpnProtocol::updateRouteGateway(QString line) ErrorCode OpenVpnProtocol::start() { - //qDebug() << "Start OpenVPN connection"; + // qDebug() << "Start OpenVPN connection"; OpenVpnProtocol::stop(); if (!QFileInfo::exists(Utils::openVpnExecPath())) { @@ -167,24 +168,25 @@ ErrorCode OpenVpnProtocol::start() QProcess p; p.setProcessChannelMode(QProcess::MergedChannels); - p.start("route", QStringList() << "-n" << "get" << "default"); + p.start("route", + QStringList() << "-n" + << "get" + << "default"); p.waitForFinished(); - QString s = p.readAll(); + QString s = p.readAll(); QRegularExpression rx(R"(gateway:\s*(\d+\.\d+\.\d+\.\d+))"); QRegularExpressionMatch match = rx.match(s); if (match.hasMatch()) { m_routeGateway = match.captured(1); qDebug() << "Set VPN route gateway" << m_routeGateway; - } - else { + } else { qWarning() << "Unable to set VPN route gateway, output:\n" << s; } #endif - -// QString vpnLogFileNamePath = Utils::systemLogPath() + "/openvpn.log"; -// Utils::createEmptyFile(vpnLogFileNamePath); + // QString vpnLogFileNamePath = Utils::systemLogPath() + "/openvpn.log"; + // Utils::createEmptyFile(vpnLogFileNamePath); uint mgmtPort = selectMgmtPort(); qDebug() << "OpenVpnProtocol::start mgmt port selected:" << mgmtPort; @@ -199,7 +201,7 @@ ErrorCode OpenVpnProtocol::start() m_openVpnProcess = IpcClient::CreatePrivilegedProcess(); if (!m_openVpnProcess) { - //qWarning() << "IpcProcess replica is not created!"; + // qWarning() << "IpcProcess replica is not created!"; setLastError(ErrorCode::AmneziaServiceConnectionFailed); return ErrorCode::AmneziaServiceConnectionFailed; } @@ -211,28 +213,25 @@ ErrorCode OpenVpnProtocol::start() return ErrorCode::AmneziaServiceConnectionFailed; } m_openVpnProcess->setProgram(PermittedProcess::OpenVPN); - QStringList arguments({"--config" , configPath(), - "--management", m_managementHost, QString::number(mgmtPort), - "--management-client"/*, "--log", vpnLogFileNamePath */ - }); + QStringList arguments({ + "--config", configPath(), "--management", m_managementHost, QString::number(mgmtPort), + "--management-client" /*, "--log", vpnLogFileNamePath */ + }); m_openVpnProcess->setArguments(arguments); qDebug() << arguments.join(" "); - connect(m_openVpnProcess.data(), &PrivilegedProcess::errorOccurred, [&](QProcess::ProcessError error) { - qDebug() << "PrivilegedProcess errorOccurred" << error; - }); + connect(m_openVpnProcess.data(), &PrivilegedProcess::errorOccurred, + [&](QProcess::ProcessError error) { qDebug() << "PrivilegedProcess errorOccurred" << error; }); - connect(m_openVpnProcess.data(), &PrivilegedProcess::stateChanged, [&](QProcess::ProcessState newState) { - qDebug() << "PrivilegedProcess stateChanged" << newState; - }); + connect(m_openVpnProcess.data(), &PrivilegedProcess::stateChanged, + [&](QProcess::ProcessState newState) { qDebug() << "PrivilegedProcess stateChanged" << newState; }); - connect(m_openVpnProcess.data(), &PrivilegedProcess::finished, this, [&]() { - setConnectionState(Vpn::ConnectionState::Disconnected); - }); + connect(m_openVpnProcess.data(), &PrivilegedProcess::finished, this, + [&]() { setConnectionState(Vpn::ConnectionState::Disconnected); }); m_openVpnProcess->start(); - //startTimeoutTimer(); + // startTimeoutTimer(); return ErrorCode::NoError; } @@ -255,7 +254,7 @@ void OpenVpnProtocol::sendInitialData() void OpenVpnProtocol::onReadyReadDataFromManagementServer() { - for (;;) { + for (;;) { QString line = m_managementServer.readLine().simplified(); if (line.isEmpty()) { @@ -268,14 +267,14 @@ void OpenVpnProtocol::onReadyReadDataFromManagementServer() if (line.contains(">INFO:OpenVPN Management Interface")) { sendInitialData(); - } else if (line.startsWith(">STATE")) { + } else if (line.startsWith(">STATE")) { if (line.contains("CONNECTED,SUCCESS")) { sendByteCount(); stopTimeoutTimer(); setConnectionState(Vpn::ConnectionState::Connected); continue; } else if (line.contains("EXITING,SIGTER")) { - //openVpnStateSigTermHandler(); + // openVpnStateSigTermHandler(); setConnectionState(Vpn::ConnectionState::Disconnecting); continue; } else if (line.contains("RECONNECTING")) { @@ -295,8 +294,7 @@ void OpenVpnProtocol::onReadyReadDataFromManagementServer() if (line.contains("FATAL")) { if (line.contains("tap-windows6 adapters on this system are currently in use or disabled")) { emit protocolError(ErrorCode::OpenVpnAdaptersInUseError); - } - else { + } else { emit protocolError(ErrorCode::OpenVpnUnknownError); } return; @@ -321,7 +319,8 @@ void OpenVpnProtocol::onReadyReadDataFromManagementServer() void OpenVpnProtocol::updateVpnGateway(const QString &line) { // line looks like - // PUSH: Received control message: 'PUSH_REPLY,route 10.8.0.1,topology net30,ping 10,ping-restart 120,ifconfig 10.8.0.6 10.8.0.5,peer-id 0,cipher AES-256-GCM' + // PUSH: Received control message: 'PUSH_REPLY,route 10.8.0.1,topology net30,ping 10,ping-restart + // 120,ifconfig 10.8.0.6 10.8.0.5,peer-id 0,cipher AES-256-GCM' QStringList params = line.split(","); for (const QString &l : params) { diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index fd748347..049201e4 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -88,6 +88,7 @@ signals: void showNotificationMessage(const QString &message); void showBusyIndicator(bool visible); + void enableTabBar(bool enabled); void hideMainWindow(); void raiseMainWindow(); diff --git a/client/ui/controllers/sitesController.cpp b/client/ui/controllers/sitesController.cpp index 98790630..d8bc99c8 100644 --- a/client/ui/controllers/sitesController.cpp +++ b/client/ui/controllers/sitesController.cpp @@ -36,12 +36,13 @@ void SitesController::addSite(QString hostname) m_sitesModel->addSite(hostname, ip); if (!ip.isEmpty()) { - m_vpnConnection->addRoutes(QStringList() << ip); - m_vpnConnection->flushDns(); + QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection, + Q_ARG(QStringList, QStringList() << ip)); } else if (Utils::ipAddressWithSubnetRegExp().exactMatch(hostname)) { - m_vpnConnection->addRoutes(QStringList() << hostname); - m_vpnConnection->flushDns(); + QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection, + Q_ARG(QStringList, QStringList() << hostname)); } + QMetaObject::invokeMethod(m_vpnConnection.get(), "flushDns", Qt::QueuedConnection); }; const auto &resolveCallback = [this, processSite](const QHostInfo &hostInfo) { @@ -70,6 +71,10 @@ void SitesController::removeSite(int index) auto hostname = m_sitesModel->data(modelIndex, SitesModel::Roles::UrlRole).toString(); m_sitesModel->removeSite(modelIndex); + QMetaObject::invokeMethod(m_vpnConnection.get(), "deleteRoutes", Qt::QueuedConnection, + Q_ARG(QStringList, QStringList() << hostname)); + QMetaObject::invokeMethod(m_vpnConnection.get(), "flushDns", Qt::QueuedConnection); + emit finished(tr("Site removed: ") + hostname); } @@ -124,8 +129,8 @@ void SitesController::importSites(bool replaceExisting) m_sitesModel->addSites(sites, replaceExisting); - m_vpnConnection->addRoutes(QStringList() << ips); - m_vpnConnection->flushDns(); + QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection, Q_ARG(QStringList, ips)); + QMetaObject::invokeMethod(m_vpnConnection.get(), "flushDns", Qt::QueuedConnection); emit finished(tr("Import completed")); } diff --git a/client/ui/pages_logic/SitesLogic.cpp b/client/ui/pages_logic/SitesLogic.cpp index b5760dfb..aaf8f73d 100644 --- a/client/ui/pages_logic/SitesLogic.cpp +++ b/client/ui/pages_logic/SitesLogic.cpp @@ -66,18 +66,14 @@ void SitesLogic::onPushButtonAddCustomSitesClicked() m_settings->addVpnSite(mode, newSite, ip); if (!ip.isEmpty()) { - QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "addRoutes", - Qt::QueuedConnection, + QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "addRoutes", Qt::QueuedConnection, Q_ARG(QStringList, QStringList() << ip)); - } - else if (Utils::ipAddressWithSubnetRegExp().exactMatch(newSite)) { - QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "addRoutes", - Qt::QueuedConnection, + } else if (Utils::ipAddressWithSubnetRegExp().exactMatch(newSite)) { + QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "addRoutes", Qt::QueuedConnection, Q_ARG(QStringList, QStringList() << newSite)); } - QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "flushDns", - Qt::QueuedConnection); + QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "flushDns", Qt::QueuedConnection); onUpdatePage(); }; @@ -124,19 +120,16 @@ void SitesLogic::onPushButtonSitesDeleteClicked(QStringList items) return; // sites.append(siteModel->data(row, 0).toString()); - if (uiLogic()->m_vpnConnection && uiLogic()->m_vpnConnection->connectionState() == VpnProtocol::Connected) { - ips.append(siteModel->data(row, 1).toString()); - } + // if (uiLogic()->m_vpnConnection && uiLogic()->m_vpnConnection->connectionState() == VpnProtocol::Connected) { + // ips.append(siteModel->data(row, 1).toString()); + // } } m_settings->removeVpnSites(mode, sites); - QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "deleteRoutes", - Qt::QueuedConnection, - Q_ARG(QStringList, ips)); + QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "deleteRoutes", Qt::QueuedConnection, Q_ARG(QStringList, ips)); - QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "flushDns", - Qt::QueuedConnection); + QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "flushDns", Qt::QueuedConnection); onUpdatePage(); } @@ -197,12 +190,9 @@ void SitesLogic::onPushButtonSitesImportClicked(const QString &fileName) m_settings->addVpnIps(mode, ips); m_settings->addVpnSites(mode, sites); - QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "addRoutes", - Qt::QueuedConnection, - Q_ARG(QStringList, ips)); + QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "addRoutes", Qt::QueuedConnection, Q_ARG(QStringList, ips)); - QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "flushDns", - Qt::QueuedConnection); + QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "flushDns", Qt::QueuedConnection); onUpdatePage(); } diff --git a/client/ui/qml/Pages2/PageDeinstalling.qml b/client/ui/qml/Pages2/PageDeinstalling.qml index 49bcdb9a..eff58055 100644 --- a/client/ui/qml/Pages2/PageDeinstalling.qml +++ b/client/ui/qml/Pages2/PageDeinstalling.qml @@ -14,6 +14,9 @@ import "../Config" PageType { id: root + Component.onCompleted: PageController.enableTabBar(false) + Component.onDestruction: PageController.enableTabBar(true) + SortFilterProxyModel { id: proxyServersModel sourceModel: ServersModel diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index d25fb659..bea239c7 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -299,6 +299,12 @@ PageType { checked: additionalClientCommands !== "" text: qsTr("Additional client configuration commands") + + onCheckedChanged: { + if (!checked) { + additionalClientCommands = "" + } + } } TextAreaType { diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml index cd0b3ee8..39cad732 100644 --- a/client/ui/qml/Pages2/PageSettingsBackup.qml +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -21,7 +21,7 @@ PageType { function onRestoreBackupFinished() { PageController.showNotificationMessage(qsTr("Settings restored from backup file")) - goToStartPage() + //goToStartPage() PageController.goToPageHome() } } diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 7be35cac..407194e1 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -166,7 +166,7 @@ PageType { questionDrawer.visible = false goToPage(PageEnum.PageDeinstalling) if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { - ConnectionController.closeVpnConnection() + ConnectionController.closeConnection() } InstallController.removeAllContainers() } diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index 6c6d1a67..e194c21c 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -14,6 +14,9 @@ import "../Config" PageType { id: root + Component.onCompleted: PageController.enableTabBar(false) + Component.onDestruction: PageController.enableTabBar(true) + Connections { target: InstallController diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 2cc64a91..5c2a13df 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -18,12 +18,12 @@ PageType { function onGoToPageHome() { tabBar.currentIndex = 0 - tabBarStackView.goToTabBarPage(PageController.getPagePath(PageEnum.PageHome)) + tabBarStackView.goToTabBarPage(PageEnum.PageHome) } function onGoToPageSettings() { tabBar.currentIndex = 2 - tabBarStackView.goToTabBarPage(PageController.getPagePath(PageEnum.PageSettings)) + tabBarStackView.goToTabBarPage(PageEnum.PageSettings) } function onGoToPageViewConfig() { @@ -37,6 +37,10 @@ PageType { tabBar.enabled = !visible } + function onEnableTabBar(enabled) { + tabBar.enabled = enabled + } + function onClosePage() { if (tabBarStackView.depth <= 1) { return diff --git a/client/ui/uilogic.cpp b/client/ui/uilogic.cpp index aefcc49c..27a4e971 100644 --- a/client/ui/uilogic.cpp +++ b/client/ui/uilogic.cpp @@ -157,9 +157,6 @@ void UiLogic::initializeUiLogic() // } m_selectedServerIndex = m_settings->defaultServerIndex(); - - qInfo().noquote() << QString("Started %1 version %2").arg(APPLICATION_NAME).arg(APP_VERSION); - qInfo().noquote() << QString("%1 (%2)").arg(QSysInfo::prettyProductName()).arg(QSysInfo::currentCpuArchitecture()); } void UiLogic::showOnStartup() From c1c68cf72dfe5e6b45e50b5535562f730b9a2928 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 10 Aug 2023 10:02:13 +0500 Subject: [PATCH 059/278] fixed ability to share warguard in native format --- CMakeLists.txt | 4 ++-- client/ui/qml/Components/ConnectButton.qml | 1 + client/ui/qml/Pages2/PageShare.qml | 8 +++++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ff60079..be5daeac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,11 +2,11 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.0.0.1 +project(${PROJECT} VERSION 4.0.1.1 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) -set(RELEASE_DATE "2023-07-31") +set(RELEASE_DATE "2023-08-10") set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml index 85cc345a..ab28d0d0 100644 --- a/client/ui/qml/Components/ConnectButton.qml +++ b/client/ui/qml/Components/ConnectButton.qml @@ -41,6 +41,7 @@ Button { anchors.right: parent.right layer.enabled: true layer.samples: 4 + layer.smooth: true layer.effect: DropShadow { anchors.fill: backgroundCircle horizontalOffset: 0 diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 6e3fecf2..e88372d6 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -310,10 +310,12 @@ PageType { function fillConnectionTypeModel() { root.connectionTypesModel = [amneziaConnectionFormat] - if (currentIndex === ContainerProps.containerFromString("OpenVpn")) { + var index = proxyContainersModel.mapToSource(currentIndex) + + if (index === ContainerProps.containerFromString("amnezia-openvpn")) { root.connectionTypesModel.push(openVpnConnectionFormat) - } else if (currentIndex === ContainerProps.containerFromString("wireGuardConnectionType")) { - root.connectionTypesModel.push(amneziaConnectionFormat) + } else if (index === ContainerProps.containerFromString("amnezia-wireguard")) { + root.connectionTypesModel.push(wireGuardConnectionFormat) } } } From 14fa0b4fd374ad80422309eac044c268221f7e20 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sun, 13 Aug 2023 11:28:32 +0500 Subject: [PATCH 060/278] added qr code scanner for ios --- client/amnezia_application.h | 1 + client/containers/containers_defs.cpp | 4 +- client/platforms/ios/QRCodeReaderBase.mm | 7 +- client/ui/controllers/importController.cpp | 53 ++++++++--- client/ui/controllers/importController.h | 13 ++- client/ui/models/containers_model.cpp | 2 +- .../models/protocols/openvpnConfigModel.cpp | 3 +- client/ui/models/servers_model.cpp | 1 - .../qml/Pages2/PageProtocolCloakSettings.qml | 2 - .../Pages2/PageProtocolOpenVpnSettings.qml | 1 - .../PageProtocolShadowSocksSettings.qml | 2 - .../ui/qml/Pages2/PageServiceSftpSettings.qml | 2 - .../Pages2/PageServiceTorWebsiteSettings.qml | 2 - .../qml/Pages2/PageSettingsServerProtocol.qml | 7 +- .../Pages2/PageSetupWizardConfigSource.qml | 3 +- .../qml/Pages2/PageSetupWizardInstalling.qml | 2 - .../PageSetupWizardProtocolSettings.qml | 1 - .../ui/qml/Pages2/PageSetupWizardQrReader.qml | 87 +++++++++++++------ client/vpnconnection.cpp | 4 +- 19 files changed, 128 insertions(+), 69 deletions(-) diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 675ffd81..02c60001 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -7,6 +7,7 @@ #include #include #include +#include #include "settings.h" #include "vpnconnection.h" diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index 1e8046bf..27f6f1dd 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -217,8 +217,8 @@ QString ContainerProps::easySetupDescription(DockerContainer container) { switch (container) { case DockerContainer::OpenVpn: return tr("I just want to increase the level of privacy"); - case DockerContainer::Cloak: return tr("Some foreign sites are blocked, but VPN providers are not blocked"); - case DockerContainer::ShadowSocks: return tr("Many foreign websites and VPN providers are blocked"); + case DockerContainer::Cloak: return tr("Many foreign websites and VPN providers are blocked"); + case DockerContainer::ShadowSocks: return tr("Some foreign sites are blocked, but VPN providers are not blocked"); default: return ""; } } diff --git a/client/platforms/ios/QRCodeReaderBase.mm b/client/platforms/ios/QRCodeReaderBase.mm index bd0dbac3..af879e2f 100644 --- a/client/platforms/ios/QRCodeReaderBase.mm +++ b/client/platforms/ios/QRCodeReaderBase.mm @@ -49,14 +49,15 @@ _videoPreviewPlayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession: _captureSession]; - CGFloat tabBarHeight = 20.0; + CGFloat statusBarHeight = [UIApplication sharedApplication].statusBarFrame.size.height; + QRect cameraRect = _qrCodeReader->cameraSize(); CGRect cameraCGRect = CGRectMake(cameraRect.x(), - cameraRect.y() + tabBarHeight, + cameraRect.y() + statusBarHeight, cameraRect.width(), cameraRect.height()); - [_videoPreviewPlayer setVideoGravity: AVLayerVideoGravityResizeAspect]; + [_videoPreviewPlayer setVideoGravity: AVLayerVideoGravityResizeAspectFill]; [_videoPreviewPlayer setFrame: cameraCGRect]; CALayer* layer = [UIApplication sharedApplication].keyWindow.layer; diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 8e2ad5d1..4ff972cc 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -42,8 +42,11 @@ namespace return ConfigTypes::Amnezia; } -#ifdef Q_OS_ANDROID +#if defined Q_OS_ANDROID ImportController *mInstance = nullptr; +#endif + +#ifdef Q_OS_ANDROID constexpr auto AndroidCameraActivity = "org.amnezia.vpn.qt.CameraActivity"; #endif } // namespace @@ -264,17 +267,6 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data) } #ifdef Q_OS_ANDROID -void ImportController::startDecodingQr() -{ - AndroidController::instance()->startQrReaderActivity(); -} - -void ImportController::stopDecodingQr() -{ - QJniObject::callStaticMethod(AndroidCameraActivity, "stopQrCodeReader", "()V"); - emit qrDecodingFinished(); -} - void ImportController::onNewQrCodeDataChunk(JNIEnv *env, jobject thiz, jstring data) { Q_UNUSED(thiz); @@ -296,6 +288,30 @@ void ImportController::onNewQrCodeDataChunk(JNIEnv *env, jobject thiz, jstring d mInstance->parseQrCodeChunk(parcelBody); } } +#endif + +#if defined Q_OS_ANDROID || defined Q_OS_IOS +void ImportController::startDecodingQr() +{ + m_qrCodeChunks.clear(); + m_totalQrCodeChunksCount = 0; + m_receivedQrCodeChunksCount = 0; + + #if defined Q_OS_IOS + m_isQrCodeProcessed = true; + #endif + #if defined Q_OS_ANDROID + AndroidController::instance()->startQrReaderActivity(); + #endif +} + +void ImportController::stopDecodingQr() +{ + #if defined Q_OS_ANDROID + QJniObject::callStaticMethod(AndroidCameraActivity, "stopQrCodeReader", "()V"); + #endif + emit qrDecodingFinished(); +} void ImportController::parseQrCodeChunk(const QString &code) { @@ -333,8 +349,10 @@ void ImportController::parseQrCodeChunk(const QString &code) bool ok = extractConfigFromQr(data); if (ok) { m_isQrCodeProcessed = false; + qDebug() << "stopDecodingQr"; stopDecodingQr(); } else { + qDebug() << "error while extracting data from qr"; m_qrCodeChunks.clear(); m_totalQrCodeChunksCount = 0; m_receivedQrCodeChunksCount = 0; @@ -344,8 +362,19 @@ void ImportController::parseQrCodeChunk(const QString &code) bool ok = extractConfigFromQr(ba); if (ok) { m_isQrCodeProcessed = false; + qDebug() << "stopDecodingQr"; stopDecodingQr(); } } } + +double ImportController::getQrCodeScanProgressBarValue() +{ + return (1.0 / m_totalQrCodeChunksCount) * m_receivedQrCodeChunksCount; +} + +QString ImportController::getQrCodeScanProgressString() +{ + return tr("Scanned %1 of %2.").arg(m_receivedQrCodeChunksCount).arg(m_totalQrCodeChunksCount); +} #endif diff --git a/client/ui/controllers/importController.h b/client/ui/controllers/importController.h index 9556bc6a..cccdf4b7 100644 --- a/client/ui/controllers/importController.h +++ b/client/ui/controllers/importController.h @@ -28,8 +28,12 @@ public slots: QString getConfig(); QString getConfigFileName(); -#if defined Q_OS_ANDROID +#if defined Q_OS_ANDROID || defined Q_OS_IOS void startDecodingQr(); + void parseQrCodeChunk(const QString &code); + + double getQrCodeScanProgressBarValue(); + QString getQrCodeScanProgressString(); #endif signals: @@ -43,10 +47,11 @@ private: QJsonObject extractOpenVpnConfig(const QString &data); QJsonObject extractWireGuardConfig(const QString &data); -#if defined Q_OS_ANDROID +#if defined Q_OS_ANDROID || defined Q_OS_IOS void stopDecodingQr(); +#endif +#if defined Q_OS_ANDROID static void onNewQrCodeDataChunk(JNIEnv *env, jobject thiz, jstring data); - void parseQrCodeChunk(const QString &code); #endif QSharedPointer m_serversModel; @@ -56,7 +61,7 @@ private: QJsonObject m_config; QString m_configFileName; -#if defined Q_OS_ANDROID +#if defined Q_OS_ANDROID || defined Q_OS_IOS QMap m_qrCodeChunks; bool m_isQrCodeProcessed; int m_totalQrCodeChunksCount; diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index 8d24e019..dd1d8d4e 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -176,7 +176,7 @@ ErrorCode ContainersModel::removeCurrentlyProcessedContainer() ErrorCode errorCode = serverController.removeContainer(credentials, dockerContainer); if (errorCode == ErrorCode::NoError) { - beginResetModel(); // todo change to begin remove rows? + beginResetModel(); m_settings->removeContainerConfig(m_currentlyProcessedServerIndex, dockerContainer); m_containers = m_settings->containers(m_currentlyProcessedServerIndex); diff --git a/client/ui/models/protocols/openvpnConfigModel.cpp b/client/ui/models/protocols/openvpnConfigModel.cpp index 1b73c987..7ef3af5b 100644 --- a/client/ui/models/protocols/openvpnConfigModel.cpp +++ b/client/ui/models/protocols/openvpnConfigModel.cpp @@ -79,8 +79,7 @@ QVariant OpenVpnConfigModel::data(const QModelIndex &index, int role) const void OpenVpnConfigModel::updateModel(const QJsonObject &config) { beginResetModel(); - m_container = - ContainerProps::containerFromString(config.value(config_key::container).toString()); // todo maybe unused + m_container = ContainerProps::containerFromString(config.value(config_key::container).toString()); m_fullConfig = config; QJsonObject protocolConfig = config.value(config_key::openvpn).toObject(); diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 364fd71f..0a117e35 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -157,7 +157,6 @@ bool ServersModel::isDefaultServerHasWriteAccess() void ServersModel::addServer(const QJsonObject &server) { - // todo beginInsertRows()? beginResetModel(); m_settings->addServer(server); m_servers = m_settings->serversArray(); diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml index cc764451..8e9085ed 100644 --- a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -13,12 +13,10 @@ import "../Components" PageType { id: root - //todo move to main? Connections { target: InstallController function onUpdateContainerFinished() { - //todo change to notification PageController.showNotificationMessage(qsTr("Settings updated successfully")) } } diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index bea239c7..ca13d7e5 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -19,7 +19,6 @@ PageType { target: InstallController function onUpdateContainerFinished() { - //todo change to notification PageController.showNotificationMessage(qsTr("Settings updated successfully")) } } diff --git a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml index 64390790..1189290f 100644 --- a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml @@ -13,12 +13,10 @@ import "../Components" PageType { id: root - //todo move to main? Connections { target: InstallController function onUpdateContainerFinished() { - //todo change to notification PageController.showNotificationMessage(qsTr("Settings updated successfully")) } } diff --git a/client/ui/qml/Pages2/PageServiceSftpSettings.qml b/client/ui/qml/Pages2/PageServiceSftpSettings.qml index d37562a1..6783fecd 100644 --- a/client/ui/qml/Pages2/PageServiceSftpSettings.qml +++ b/client/ui/qml/Pages2/PageServiceSftpSettings.qml @@ -15,12 +15,10 @@ import "../Components" PageType { id: root - //todo move to main? Connections { target: InstallController function onUpdateContainerFinished() { - //todo change to notification PageController.showNotificationMessage(qsTr("Settings updated successfully")) } } diff --git a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml index 5ddb9ed6..40e1b339 100644 --- a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml +++ b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml @@ -16,12 +16,10 @@ import "../Components" PageType { id: root - //todo move to main? Connections { target: InstallController function onUpdateContainerFinished() { - //todo change to notification PageController.showNotificationMessage(qsTr("Settings updated successfully")) } } diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml index e417eea9..db86f7b2 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -55,16 +55,15 @@ PageType { anchors.topMargin: 32 ListView { - // todo change id naming - id: container + id: protocols width: parent.width - height: container.contentItem.height + height: protocols.contentItem.height clip: true interactive: false model: ProtocolsModel delegate: Item { - implicitWidth: container.width + implicitWidth: protocols.width implicitHeight: delegateContent.implicitHeight ColumnLayout { diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index cd0c08fb..f0155667 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -17,6 +17,7 @@ PageType { target: ImportController function onQrDecodingFinished() { + closePage() goToPage(PageEnum.PageSetupWizardViewConfig) } } @@ -86,7 +87,7 @@ It's okay if a friend passed the code.") clickedFunction: function() { ImportController.startDecodingQr() -// goToPage(PageEnum.PageSetupWizardQrReader) + goToPage(PageEnum.PageSetupWizardQrReader) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index e194c21c..3f135c15 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -68,7 +68,6 @@ PageType { } FlickableType { - id: fl anchors.fill: parent contentHeight: content.height @@ -82,7 +81,6 @@ PageType { spacing: 16 ListView { - // todo change id naming id: container width: parent.width height: container.contentItem.height diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 65e00da8..f9d55ddf 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -233,7 +233,6 @@ PageType { } Component.onCompleted: { - //todo move to protocols model? var defaultContainerProto = ContainerProps.defaultProtocol(dockerContainer) if (ProtocolProps.defaultPort(defaultContainerProto) < 0) { diff --git a/client/ui/qml/Pages2/PageSetupWizardQrReader.qml b/client/ui/qml/Pages2/PageSetupWizardQrReader.qml index 65d233ef..695175fd 100644 --- a/client/ui/qml/Pages2/PageSetupWizardQrReader.qml +++ b/client/ui/qml/Pages2/PageSetupWizardQrReader.qml @@ -14,39 +14,76 @@ import "../Config" PageType { id: root - ColumnLayout { - anchors.fill: parent + BackButtonType { + id: backButton + anchors.left: parent.left + anchors.top: parent.top - spacing: 0 + anchors.topMargin: 20 + } - BackButtonType { - Layout.topMargin: 20 - } + ParagraphTextType { + id: header - ParagraphTextType { - Layout.fillWidth: true + property string progressString - text: qsTr("Point the camera at the QR code and hold for a couple of seconds.") - } + anchors.left: parent.left + anchors.top: backButton.bottom + anchors.right: parent.right - ProgressBarType { + anchors.leftMargin: 16 + anchors.rightMargin: 16 - } + text: qsTr("Point the camera at the QR code and hold for a couple of seconds. ") + progressString + } - Item { - Layout.fillHeight: true - Layout.fillWidth: true + ProgressBarType { + id: progressBar - QRCodeReader { - id: qrCodeReader - Component.onCompleted: { - qrCodeReader.setCameraSize(Qt.rect(parent.x, - parent.y, - parent.width, - parent.height)) - qrCodeReader.startReading() - } - } + anchors.left: parent.left + anchors.top: header.bottom + anchors.right: parent.right + + anchors.leftMargin: 16 + anchors.rightMargin: 16 + } + + Rectangle { + id: qrCodeRectange + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + anchors.top: progressBar.bottom + + anchors.topMargin: 34 + anchors.leftMargin: 16 + anchors.rightMargin: 16 + anchors.bottomMargin: 34 + + color: "transparent" + //radius: 16 + + QRCodeReader { + id: qrCodeReader + + onCodeReaded: function(code) { + ImportController.parseQrCodeChunk(code) + progressBar.value = ImportController.getQrCodeScanProgressBarValue() + header.progressString = ImportController.getQrCodeScanProgressString() + } + + Component.onCompleted: { + console.log(qrCodeRectange.x) + console.log(qrCodeRectange.y) + console.log(qrCodeRectange.width) + + qrCodeReader.setCameraSize(Qt.rect(qrCodeRectange.x, + qrCodeRectange.y, + qrCodeRectange.width, + qrCodeRectange.height)) + qrCodeReader.startReading() + } + Component.onDestruction: qrCodeReader.stopReading() } } } diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index 5f3511d4..99ca700d 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -96,7 +96,7 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state) #endif #ifdef Q_OS_IOS - if (state == VpnProtocol::Connected) { + if (state == Vpn::ConnectionState::Connected) { m_checkTimer.start(); } else { @@ -337,7 +337,7 @@ void VpnConnection::connectToVpn(int serverIndex, if (!iosVpnProtocol->initialize()) { qDebug() << QString("Init failed") ; - emit VpnProtocol::Error; + emit Vpn::ConnectionState::Error; iosVpnProtocol->deleteLater(); return; } From e157160337a43671342857c4b8495e95f4e4e2b6 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 16 Aug 2023 12:11:34 +0500 Subject: [PATCH 061/278] added disconnection from vpn when closing application for desktop - many small ui fixes --- client/amnezia_application.cpp | 51 ++++++------------- client/amnezia_application.h | 8 +-- .../ui/controllers/connectionController.cpp | 17 +++++++ client/ui/controllers/connectionController.h | 2 + client/ui/controllers/installController.cpp | 2 +- client/ui/controllers/installController.h | 2 +- client/ui/controllers/pageController.h | 2 + client/ui/controllers/settingsController.cpp | 18 ++++++- client/ui/controllers/settingsController.h | 5 ++ client/ui/models/servers_model.cpp | 5 ++ client/ui/models/servers_model.h | 3 +- .../qml/Components/ShareConnectionDrawer.qml | 2 + client/ui/qml/Controls2/ListViewType.qml | 6 +++ .../qml/Pages2/PageProtocolCloakSettings.qml | 2 +- .../Pages2/PageProtocolOpenVpnSettings.qml | 5 +- .../PageProtocolShadowSocksSettings.qml | 2 +- .../ui/qml/Pages2/PageSettingsServerData.qml | 7 +++ .../ui/qml/Pages2/PageSettingsServerInfo.qml | 15 +++--- .../qml/Pages2/PageSettingsServerProtocol.qml | 2 + .../qml/Pages2/PageSetupWizardInstalling.qml | 5 +- .../PageSetupWizardProtocolSettings.qml | 1 + .../qml/Pages2/PageSetupWizardViewConfig.qml | 1 + client/ui/qml/Pages2/PageShare.qml | 15 +++--- client/ui/qml/main2.qml | 7 +++ 24 files changed, 121 insertions(+), 64 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 20b5ac3d..d9120042 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -57,36 +57,13 @@ AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecond AmneziaApplication::~AmneziaApplication() { - // emit hide(); - - // #ifdef AMNEZIA_DESKTOP - // if (m_vpnConnection->connectionState() != Vpn::ConnectionState::Disconnected) { - // m_vpnConnection->disconnectFromVpn(); - // for (int i = 0; i < 50; i++) { - // qApp->processEvents(QEventLoop::ExcludeUserInputEvents); - // QThread::msleep(100); - // if (m_vpnConnection->isDisconnected()) { - // break; - // } - // } - // } - // #endif - - // m_vpnConnection->deleteLater(); - // m_vpnConnectionThread.quit(); - // m_vpnConnectionThread.wait(3000); - - // qDebug() << "Application closed"; + m_vpnConnectionThread.quit(); + m_vpnConnectionThread.wait(3000); if (m_engine) { QObject::disconnect(m_engine, 0, 0, 0); delete m_engine; } - - if (m_protocolProps) - delete m_protocolProps; - if (m_containerProps) - delete m_containerProps; } void AmneziaApplication::init() @@ -208,11 +185,11 @@ void AmneziaApplication::registerTypes() qmlRegisterType("QRCodeReader", 1, 0, "QRCodeReader"); - m_containerProps = new ContainerProps; - qmlRegisterSingletonInstance("ContainerProps", 1, 0, "ContainerProps", m_containerProps); + m_containerProps.reset(new ContainerProps()); + qmlRegisterSingletonInstance("ContainerProps", 1, 0, "ContainerProps", m_containerProps.get()); - m_protocolProps = new ProtocolProps; - qmlRegisterSingletonInstance("ProtocolProps", 1, 0, "ProtocolProps", m_protocolProps); + m_protocolProps.reset(new ProtocolProps()); + qmlRegisterSingletonInstance("ProtocolProps", 1, 0, "ProtocolProps", m_protocolProps.get()); qmlRegisterSingletonType(QUrl("qrc:/ui/qml/Filters/ContainersModelFilters.qml"), "ContainersModelFilters", 1, 0, "ContainersModelFilters"); @@ -232,9 +209,13 @@ void AmneziaApplication::loadFonts() void AmneziaApplication::loadTranslator() { auto locale = m_settings->getAppLanguage(); - m_translator = new QTranslator; - if (m_translator->load(locale, QString("amneziavpn"), QLatin1String("_"), QLatin1String(":/i18n"))) { - installTranslator(m_translator); + if (locale != QLocale::English) { + m_translator.reset(new QTranslator()); + if (m_translator->load(locale, QString("amneziavpn"), QLatin1String("_"), QLatin1String(":/i18n"))) { + if (QCoreApplication::installTranslator(m_translator.get())) { + m_settings->setAppLanguage(locale); + } + } } } @@ -242,7 +223,7 @@ void AmneziaApplication::updateTranslator(const QLocale &locale) { QResource::registerResource(":/translations.qrc"); if (!m_translator->isEmpty()) - QCoreApplication::removeTranslator(m_translator); + QCoreApplication::removeTranslator(m_translator.get()); if (locale == QLocale::English) { m_settings->setAppLanguage(locale); @@ -250,7 +231,7 @@ void AmneziaApplication::updateTranslator(const QLocale &locale) } if (m_translator->load(locale, QString("amneziavpn"), QLatin1String("_"), QLatin1String(":/i18n"))) { - if (QCoreApplication::installTranslator(m_translator)) { + if (QCoreApplication::installTranslator(m_translator.get())) { m_settings->setAppLanguage(locale); } @@ -351,7 +332,7 @@ void AmneziaApplication::initControllers() m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_settings, m_configurator)); m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get()); - m_settingsController.reset(new SettingsController(m_serversModel, m_containersModel, m_settings)); + m_settingsController.reset(new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_settings)); m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get()); m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel)); diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 02c60001..c4f33753 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -79,15 +79,15 @@ private: std::shared_ptr m_settings; std::shared_ptr m_configurator; - ContainerProps *m_containerProps {}; - ProtocolProps *m_protocolProps {}; + QSharedPointer m_containerProps; + QSharedPointer m_protocolProps; - QTranslator *m_translator; + QSharedPointer m_translator; QCommandLineParser m_parser; QSharedPointer m_containersModel; QSharedPointer m_serversModel; - QScopedPointer m_languageModel; + QSharedPointer m_languageModel; QScopedPointer m_protocolsModel; QSharedPointer m_sitesModel; diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index 7d1b6bed..45f24d7f 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -17,6 +17,23 @@ ConnectionController::ConnectionController(const QSharedPointer &s Qt::QueuedConnection); } +ConnectionController::~ConnectionController() +{ +// todo use ConnectionController instead of using m_vpnConnection directly +#ifdef AMNEZIA_DESKTOP + if (m_vpnConnection->connectionState() != Vpn::ConnectionState::Disconnected) { + m_vpnConnection->disconnectFromVpn(); + for (int i = 0; i < 50; i++) { + qApp->processEvents(QEventLoop::ExcludeUserInputEvents); + QThread::msleep(100); + if (m_vpnConnection->isDisconnected()) { + break; + } + } + } +#endif +} + void ConnectionController::openConnection() { int serverIndex = m_serversModel->getDefaultServerIndex(); diff --git a/client/ui/controllers/connectionController.h b/client/ui/controllers/connectionController.h index a33d30c2..5a35f9d8 100644 --- a/client/ui/controllers/connectionController.h +++ b/client/ui/controllers/connectionController.h @@ -19,6 +19,8 @@ public: const QSharedPointer &containersModel, const QSharedPointer &vpnConnection, QObject *parent = nullptr); + ~ConnectionController(); + bool isConnected() const; bool isConnectionInProgress() const; QString connectionStateText() const; diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 49c77708..7e71aef8 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -173,7 +173,7 @@ void InstallController::installContainer(DockerContainer container, QJsonObject "All installed containers have been added to the application"); } - emit installContainerFinished(finishMessage); + emit installContainerFinished(finishMessage, ContainerProps::containerService(container) == ServiceType::Other); return; } diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index 38e2e780..b74835d9 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -42,7 +42,7 @@ public slots: void setEncryptedPassphrase(QString passphrase); signals: - void installContainerFinished(const QString &finishMessage); + void installContainerFinished(const QString &finishMessage, bool isServiceInstall); void installServerFinished(const QString &finishMessage); void updateContainerFinished(); diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 049201e4..1948ed11 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -79,6 +79,8 @@ signals: void goToPageHome(); void goToPageSettings(); void goToPageViewConfig(); + void goToPageSettingsServerServices(); + void closePage(); void restorePageHomeState(bool isContainerInstalled = false); diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index 571b03e8..531de14d 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -8,8 +8,13 @@ SettingsController::SettingsController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, + const QSharedPointer &languageModel, const std::shared_ptr &settings, QObject *parent) - : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings) + : QObject(parent), + m_serversModel(serversModel), + m_containersModel(containersModel), + m_languageModel(languageModel), + m_settings(settings) { m_appVersion = QString("%1: %2 (%3)").arg(tr("Software version"), QString(APP_MAJOR_VERSION), __DATE__); } @@ -95,6 +100,8 @@ void SettingsController::restoreAppConfig() bool ok = m_settings->restoreAppConfig(data); if (ok) { m_serversModel->resetModel(); + m_languageModel->changeLanguage( + static_cast(m_languageModel->getCurrentLanguageIndex())); emit restoreBackupFinished(); } else { emit changeSettingsErrorOccurred(tr("Backup file is corrupted")); @@ -110,6 +117,15 @@ void SettingsController::clearSettings() { m_settings->clearSettings(); m_serversModel->resetModel(); + m_languageModel->changeLanguage( + static_cast(m_languageModel->getCurrentLanguageIndex())); + emit changeSettingsFinished(tr("All settings have been reset to default values")); +} + +void SettingsController::clearCachedProfiles() +{ + m_containersModel->clearCachedProfiles(); + emit changeSettingsFinished(tr("Cached profiles cleared")); } bool SettingsController::isAutoConnectEnabled() diff --git a/client/ui/controllers/settingsController.h b/client/ui/controllers/settingsController.h index ffd72f69..a0a29ebf 100644 --- a/client/ui/controllers/settingsController.h +++ b/client/ui/controllers/settingsController.h @@ -4,6 +4,7 @@ #include #include "ui/models/containers_model.h" +#include "ui/models/languageModel.h" #include "ui/models/servers_model.h" class SettingsController : public QObject @@ -12,6 +13,7 @@ class SettingsController : public QObject public: explicit SettingsController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, + const QSharedPointer &languageModel, const std::shared_ptr &settings, QObject *parent = nullptr); Q_PROPERTY(QString primaryDns READ getPrimaryDns WRITE setPrimaryDns NOTIFY primaryDnsChanged) @@ -41,6 +43,7 @@ public slots: QString getAppVersion(); void clearSettings(); + void clearCachedProfiles(); bool isAutoConnectEnabled(); void toggleAutoConnect(bool enable); @@ -51,11 +54,13 @@ signals: void loggingStateChanged(); void restoreBackupFinished(); + void changeSettingsFinished(const QString &finishedMessage); void changeSettingsErrorOccurred(const QString &errorMessage); private: QSharedPointer m_serversModel; QSharedPointer m_containersModel; + QSharedPointer m_languageModel; std::shared_ptr m_settings; QString m_appVersion; diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 0a117e35..7eea94e5 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -6,6 +6,8 @@ ServersModel::ServersModel(std::shared_ptr settings, QObject *parent) m_servers = m_settings->serversArray(); m_defaultServerIndex = m_settings->defaultServerIndex(); m_currentlyProcessedServerIndex = m_defaultServerIndex; + + connect(this, &ServersModel::defaultServerIndexChanged, this, &ServersModel::defaultServerNameChanged); } int ServersModel::rowCount(const QModelIndex &parent) const @@ -27,6 +29,9 @@ bool ServersModel::setData(const QModelIndex &index, const QVariant &value, int server.insert(config_key::description, value.toString()); m_settings->editServer(index.row(), server); m_servers.replace(index.row(), server); + if (index.row() == m_defaultServerIndex) { + emit defaultServerNameChanged(); + } break; } default: { diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index 60ec35b2..d7b15844 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -31,7 +31,7 @@ public: void resetModel(); Q_PROPERTY(int defaultIndex READ getDefaultServerIndex WRITE setDefaultServerIndex NOTIFY defaultServerIndexChanged) - Q_PROPERTY(QString defaultServerName READ getDefaultServerName NOTIFY defaultServerIndexChanged) + Q_PROPERTY(QString defaultServerName READ getDefaultServerName NOTIFY defaultServerNameChanged) Q_PROPERTY(QString defaultServerHostName READ getDefaultServerHostName NOTIFY defaultServerIndexChanged) Q_PROPERTY(int currentlyProcessedIndex READ getCurrentlyProcessedServerIndex WRITE setCurrentlyProcessedServerIndex NOTIFY currentlyProcessedServerIndexChanged) @@ -65,6 +65,7 @@ protected: signals: void currentlyProcessedServerIndexChanged(const int index); void defaultServerIndexChanged(); + void defaultServerNameChanged(); private: ServerCredentials serverCredentials(int index) const; diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 3f27396b..7ad724ea 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -80,6 +80,7 @@ DrawerType { configText.selectAll() configText.copy() configText.select(0, 0) + PageController.showNotificationMessage(qsTr("Copied")) } } @@ -92,6 +93,7 @@ DrawerType { pressedColor: Qt.rgba(1, 1, 1, 0.12) disabledColor: "#878B91" textColor: "#D7D8DB" + borderWidth: 1 text: qsTr("Show content") diff --git a/client/ui/qml/Controls2/ListViewType.qml b/client/ui/qml/Controls2/ListViewType.qml index 4251f0fd..926ec038 100644 --- a/client/ui/qml/Controls2/ListViewType.qml +++ b/client/ui/qml/Controls2/ListViewType.qml @@ -8,11 +8,17 @@ ListView { id: menuContent property var rootWidth + property var selectedText + property string imageSource + property var clickedFunction + property bool dividerVisible: false + currentIndex: 0 + width: rootWidth height: menuContent.contentItem.height diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml index 8e9085ed..c19118b5 100644 --- a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -102,6 +102,7 @@ PageType { headerText: qsTr("Port") textFieldText: port + textField.maximumLength: 5 textField.onEditingFinished: { if (textFieldText !== port) { @@ -114,7 +115,6 @@ PageType { id: cipherDropDown Layout.fillWidth: true Layout.topMargin: 16 - implicitHeight: 74 descriptionText: qsTr("Cipher") headerText: qsTr("Cipher") diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index ca13d7e5..a8f7bb77 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -133,6 +133,7 @@ PageType { headerText: qsTr("Port") textFieldText: port + textField.maximumLength: 5 textField.onEditingFinished: { if (textFieldText !== port) { @@ -161,7 +162,6 @@ PageType { id: hashDropDown Layout.fillWidth: true Layout.topMargin: 20 - implicitHeight: 74 enabled: !autoNegotiateEncryprionSwitcher.checked @@ -208,7 +208,6 @@ PageType { id: cipherDropDown Layout.fillWidth: true Layout.topMargin: 16 - implicitHeight: 74 enabled: !autoNegotiateEncryprionSwitcher.checked @@ -393,7 +392,7 @@ PageType { } } } - } + } } QuestionDrawer { diff --git a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml index 1189290f..38a6be06 100644 --- a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml @@ -88,6 +88,7 @@ PageType { headerText: qsTr("Port") textFieldText: port + textField.maximumLength: 5 textField.onEditingFinished: { if (textFieldText !== port) { @@ -100,7 +101,6 @@ PageType { id: cipherDropDown Layout.fillWidth: true Layout.topMargin: 20 - implicitHeight: 74 descriptionText: qsTr("Cipher") headerText: qsTr("Cipher") diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 407194e1..9ff99193 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -50,6 +50,13 @@ PageType { } } + Connections { + target: SettingsController + function onChangeSettingsFinished(finishedMessage) { + PageController.showNotificationMessage(finishedMessage) + } + } + Connections { target: ServersModel diff --git a/client/ui/qml/Pages2/PageSettingsServerInfo.qml b/client/ui/qml/Pages2/PageSettingsServerInfo.qml index cf748f54..62a7a67b 100644 --- a/client/ui/qml/Pages2/PageSettingsServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsServerInfo.qml @@ -18,6 +18,14 @@ import "../Components" PageType { id: root + Connections { + target: PageController + + function onGoToPageSettingsServerServices() { + tabBar.currentIndex = 1 + } + } + SortFilterProxyModel { id: proxyServersModel sourceModel: ServersModel @@ -99,13 +107,6 @@ PageType { BasicButtonType { Layout.fillWidth: true - 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("Save") onClicked: { diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml index db86f7b2..f1f067c2 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -107,6 +107,8 @@ PageType { width: parent.width + visible: ServersModel.isCurrentlyProcessedServerHasWriteAccess() + text: qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() textColor: "#EB5757" diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index 3f135c15..10daa0be 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -20,13 +20,16 @@ PageType { Connections { target: InstallController - function onInstallContainerFinished(finishedMessage) { + function onInstallContainerFinished(finishedMessage, isServiceInstall) { goToStartPage() if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) { PageController.restorePageHomeState(true) } else if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSettings)) { goToPage(PageEnum.PageSettingsServersList, false) goToPage(PageEnum.PageSettingsServerInfo, false) + if (isServiceInstall) { + PageController.goToPageSettingsServerServices() + } } else { goToPage(PageEnum.PageHome) } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index f9d55ddf..f5f07609 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -211,6 +211,7 @@ PageType { Layout.topMargin: 16 headerText: qsTr("Port") + textField.maximumLength: 5 } Rectangle { diff --git a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml index 5f52a5ba..222fbd11 100644 --- a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml +++ b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml @@ -79,6 +79,7 @@ PageType { Layout.fillWidth: true text: ImportController.getConfigFileName() + wrapMode: Text.Wrap } } diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index e88372d6..f2ed4607 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -161,11 +161,9 @@ PageType { Layout.fillWidth: true Layout.topMargin: 24 - implicitHeight: 74 - drawerHeight: 0.4375 - descriptionText: qsTr("Server and service") + descriptionText: accessTypeSelector.currentIndex === 0 ? qsTr("Server and service") : qsTr("Server") headerText: qsTr("Server") listView: ListViewType { @@ -261,7 +259,7 @@ PageType { rootWidth: root.width dividerVisible: true - imageSource: "qrc:/images/controls/chevron-right.svg" + imageSource: "qrc:/images/controls/check.svg" model: SortFilterProxyModel { id: proxyContainersModel @@ -327,13 +325,11 @@ PageType { DropDownType { id: exportTypeSelector - property int currentIndex + property int currentIndex: 0 Layout.fillWidth: true Layout.topMargin: 16 - implicitHeight: 74 - drawerHeight: 0.4375 visible: accessTypeSelector.currentIndex === 0 @@ -343,13 +339,15 @@ PageType { headerText: qsTr("Connection format") listView: ListViewType { + id: exportTypeListView + rootWidth: root.width dividerVisible: true imageSource: "qrc:/images/controls/chevron-right.svg" model: root.connectionTypesModel - currentIndex: 0 + currentIndex: exportTypeSelector.currentIndex clickedFunction: function() { exportTypeSelector.text = selectedText @@ -375,6 +373,7 @@ PageType { enabled: shareButtonEnabled text: qsTr("Share") + imageSource: "qrc:/images/controls/share-2.svg" onClicked: { if (accessTypeSelector.currentIndex === 0) { diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index f07bcf5d..6ae434d7 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -81,6 +81,13 @@ Window { } } + Connections { + target: SettingsController + function onChangeSettingsFinished(finishedMessage) { + PageController.showNotificationMessage(finishedMessage) + } + } + Item { anchors.right: parent.right anchors.left: parent.left From a8deb3593be83eae01b17d5b8aba8936cb59f22f Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 16 Aug 2023 22:45:05 +0500 Subject: [PATCH 062/278] dropdown list fixes to match design layout --- client/resources.qrc | 3 +- .../Components/SettingsContainersListView.qml | 179 +++++++----------- .../qml/Controls2/ListViewWithLabelsType.qml | 70 +++++++ ...pe.qml => ListViewWithRadioButtonType.qml} | 21 +- client/ui/qml/Controls2/PageType.qml | 10 + .../qml/Pages2/PageProtocolCloakSettings.qml | 2 +- .../Pages2/PageProtocolOpenVpnSettings.qml | 6 +- .../PageProtocolShadowSocksSettings.qml | 2 +- .../ui/qml/Pages2/PageServiceSftpSettings.qml | 10 +- .../Pages2/PageServiceTorWebsiteSettings.qml | 1 + .../qml/Pages2/PageSettingsSplitTunneling.qml | 2 +- client/ui/qml/Pages2/PageShare.qml | 10 +- 12 files changed, 177 insertions(+), 139 deletions(-) create mode 100644 client/ui/qml/Controls2/ListViewWithLabelsType.qml rename client/ui/qml/Controls2/{ListViewType.qml => ListViewWithRadioButtonType.qml} (85%) diff --git a/client/resources.qrc b/client/resources.qrc index 2238f25d..782b6446 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -250,7 +250,7 @@ ui/qml/Controls2/BackButtonType.qml ui/qml/Pages2/PageSettingsServerProtocol.qml ui/qml/Components/TransportProtoSelector.qml - ui/qml/Controls2/ListViewType.qml + ui/qml/Controls2/ListViewWithRadioButtonType.qml images/controls/radio-button.svg images/controls/radio-button-inner-circle.png images/controls/radio-button-pressed.svg @@ -285,5 +285,6 @@ ui/qml/Controls2/TextAreaType.qml images/controls/trash.svg images/controls/more-vertical.svg + ui/qml/Controls2/ListViewWithLabelsType.qml diff --git a/client/ui/qml/Components/SettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml index bb578899..2489323b 100644 --- a/client/ui/qml/Components/SettingsContainersListView.qml +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -22,133 +22,86 @@ ListView { clip: true interactive: false - ButtonGroup { - id: containersRadioButtonGroup - } - delegate: Item { implicitWidth: root.width - implicitHeight: containerRadioButton.implicitHeight + implicitHeight: delegateContent.implicitHeight - RadioButton { - id: containerRadioButton + ColumnLayout { + id: delegateContent - implicitWidth: parent.width - implicitHeight: containerRadioButtonContent.implicitHeight + anchors.fill: parent - hoverEnabled: true + LabelWithButtonType { + implicitWidth: parent.width - ButtonGroup.group: containersRadioButtonGroup + text: name + descriptionText: description + rightImageSource: isInstalled ? "qrc:/images/controls/chevron-right.svg" : "qrc:/images/controls/download.svg" - indicator: Rectangle { - anchors.fill: parent - color: containerRadioButton.hovered ? Qt.rgba(1, 1, 1, 0.08) : "transparent" + clickedFunction: function() { + if (isInstalled) { + var containerIndex = root.model.mapToSource(index) + ContainersModel.setCurrentlyProcessedContainerIndex(containerIndex) - Behavior on color { - PropertyAnimation { duration: 200 } - } - } - - checkable: isInstalled - - RowLayout { - id: containerRadioButtonContent - anchors.fill: parent - - anchors.rightMargin: 16 - anchors.leftMargin: 16 - - z: 1 - - ColumnLayout { - Layout.topMargin: 20 - Layout.bottomMargin: 20 - - ListItemTitleType { - Layout.fillWidth: true - - text: name - } - - CaptionTextType { - Layout.fillWidth: true - - text: description - color: "#878B91" - } - } - - Image { - source: isInstalled ? "qrc:/images/controls/chevron-right.svg" : "qrc:/images/controls/download.svg" - - width: 24 - height: 24 - - Layout.rightMargin: 8 - } - } - - onClicked: { - if (isInstalled) { - var containerIndex = root.model.mapToSource(index) - ContainersModel.setCurrentlyProcessedContainerIndex(containerIndex) - - if (config[ContainerProps.containerTypeToString(containerIndex)]["isThirdPartyConfig"]) { - ProtocolsModel.updateModel(config) - goToPage(PageEnum.PageProtocolRaw) - return - } - - switch (containerIndex) { - case ContainerEnum.OpenVpn: { - OpenVpnConfigModel.updateModel(config) - goToPage(PageEnum.PageProtocolOpenVpnSettings) - break - } - case ContainerEnum.WireGuard: { - ProtocolsModel.updateModel(config) - goToPage(PageEnum.PageProtocolRaw) -// WireGuardConfigModel.updateModel(config) -// goToPage(PageEnum.PageProtocolWireGuardSettings) - break - } - case ContainerEnum.Ipsec: { - ProtocolsModel.updateModel(config) - goToPage(PageEnum.PageProtocolRaw) -// Ikev2ConfigModel.updateModel(config) -// goToPage(PageEnum.PageProtocolIKev2Settings) - break - } - case ContainerEnum.Sftp: { - SftpConfigModel.updateModel(config) - goToPage(PageEnum.PageServiceSftpSettings) - break - } - case ContainerEnum.TorWebSite: { - goToPage(PageEnum.PageServiceTorWebsiteSettings) - break - } - - default: { - if (serviceType !== ProtocolEnum.Other) { //todo disable settings for dns container + if (config[ContainerProps.containerTypeToString(containerIndex)]["isThirdPartyConfig"]) { ProtocolsModel.updateModel(config) - goToPage(PageEnum.PageSettingsServerProtocol) + goToPage(PageEnum.PageProtocolRaw) + return } - } - } - } else { - ContainersModel.setCurrentlyProcessedContainerIndex(root.model.mapToSource(index)) - InstallController.setShouldCreateServer(false) - goToPage(PageEnum.PageSetupWizardProtocolSettings) + switch (containerIndex) { + case ContainerEnum.OpenVpn: { + OpenVpnConfigModel.updateModel(config) + goToPage(PageEnum.PageProtocolOpenVpnSettings) + break + } + case ContainerEnum.WireGuard: { + ProtocolsModel.updateModel(config) + goToPage(PageEnum.PageProtocolRaw) + // WireGuardConfigModel.updateModel(config) + // goToPage(PageEnum.PageProtocolWireGuardSettings) + break + } + case ContainerEnum.Ipsec: { + ProtocolsModel.updateModel(config) + goToPage(PageEnum.PageProtocolRaw) + // Ikev2ConfigModel.updateModel(config) + // goToPage(PageEnum.PageProtocolIKev2Settings) + break + } + case ContainerEnum.Sftp: { + SftpConfigModel.updateModel(config) + goToPage(PageEnum.PageServiceSftpSettings) + break + } + case ContainerEnum.TorWebSite: { + goToPage(PageEnum.PageServiceTorWebsiteSettings) + break + } + + default: { + if (serviceType !== ProtocolEnum.Other) { //todo disable settings for dns container + ProtocolsModel.updateModel(config) + goToPage(PageEnum.PageSettingsServerProtocol) + } + } + } + + } else { + ContainersModel.setCurrentlyProcessedContainerIndex(root.model.mapToSource(index)) + InstallController.setShouldCreateServer(false) + goToPage(PageEnum.PageSetupWizardProtocolSettings) + } + } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + enabled: false } } - MouseArea { - anchors.fill: containerRadioButton - cursorShape: Qt.PointingHandCursor - enabled: false - } + DividerType {} } } } diff --git a/client/ui/qml/Controls2/ListViewWithLabelsType.qml b/client/ui/qml/Controls2/ListViewWithLabelsType.qml new file mode 100644 index 00000000..5b614c43 --- /dev/null +++ b/client/ui/qml/Controls2/ListViewWithLabelsType.qml @@ -0,0 +1,70 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import "TextTypes" + +ListView { + id: menuContent + + property var rootWidth + + property var selectedText + + property string imageSource: "qrc:/images/controls/check.svg" + + property var clickedFunction + + property bool dividerVisible: false + + currentIndex: 0 + + 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 + + LabelWithButtonType { + Layout.fillWidth: true + + text: name + rightImageSource: imageSource + + clickedFunction: function() { + menuContent.currentIndex = index + menuContent.selectedText = name + if (menuContent.clickedFunction && typeof menuContent.clickedFunction === "function") { + menuContent.clickedFunction() + } + } + } + + DividerType { + Layout.fillWidth: true + Layout.bottomMargin: 4 + + visible: dividerVisible + } + } + + Component.onCompleted: { + if (menuContent.currentIndex === index) { + menuContent.selectedText = name + } + } + } +} diff --git a/client/ui/qml/Controls2/ListViewType.qml b/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml similarity index 85% rename from client/ui/qml/Controls2/ListViewType.qml rename to client/ui/qml/Controls2/ListViewWithRadioButtonType.qml index 926ec038..f7f99dec 100644 --- a/client/ui/qml/Controls2/ListViewType.qml +++ b/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml @@ -11,12 +11,10 @@ ListView { property var selectedText - property string imageSource + property string imageSource: "qrc:/images/controls/check.svg" property var clickedFunction - property bool dividerVisible: false - currentIndex: 0 width: rootWidth @@ -53,6 +51,12 @@ ListView { Behavior on color { PropertyAnimation { duration: 200 } } + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + enabled: false + } } RowLayout { @@ -73,8 +77,8 @@ ListView { } Image { - source: imageSource ? imageSource : "qrc:/images/controls/check.svg" - visible: imageSource ? true : radioButton.checked + source: imageSource + visible: radioButton.checked width: 24 height: 24 @@ -94,13 +98,6 @@ ListView { } } } - - DividerType { - Layout.fillWidth: true - Layout.bottomMargin: 4 - - visible: dividerVisible - } } Component.onCompleted: { diff --git a/client/ui/qml/Controls2/PageType.qml b/client/ui/qml/Controls2/PageType.qml index 296fcc8c..cb6fa142 100644 --- a/client/ui/qml/Controls2/PageType.qml +++ b/client/ui/qml/Controls2/PageType.qml @@ -28,4 +28,14 @@ Item { root.stackView.pop() } } + + MouseArea { + z: -1 + anchors.fill: parent + + onClicked: { + console.log("base mouse area pressed") + focus = true + } + } } diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml index c19118b5..7273b86d 100644 --- a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -119,7 +119,7 @@ PageType { descriptionText: qsTr("Cipher") headerText: qsTr("Cipher") - listView: ListViewType { + listView: ListViewWithRadioButtonType { id: cipherListView rootWidth: root.width diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index a8f7bb77..fb2258c5 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -168,7 +168,7 @@ PageType { descriptionText: qsTr("Hash") headerText: qsTr("Hash") - listView: ListViewType { + listView: ListViewWithRadioButtonType { id: hashListView rootWidth: root.width @@ -214,7 +214,7 @@ PageType { descriptionText: qsTr("Cipher") headerText: qsTr("Cipher") - listView: ListViewType { + listView: ListViewWithRadioButtonType { id: cipherListView rootWidth: root.width @@ -392,7 +392,7 @@ PageType { } } } - } + } } QuestionDrawer { diff --git a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml index 38a6be06..7b78bc07 100644 --- a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml @@ -105,7 +105,7 @@ PageType { descriptionText: qsTr("Cipher") headerText: qsTr("Cipher") - listView: ListViewType { + listView: ListViewWithRadioButtonType { id: cipherListView rootWidth: root.width diff --git a/client/ui/qml/Pages2/PageServiceSftpSettings.qml b/client/ui/qml/Pages2/PageServiceSftpSettings.qml index 6783fecd..32c0c170 100644 --- a/client/ui/qml/Pages2/PageServiceSftpSettings.qml +++ b/client/ui/qml/Pages2/PageServiceSftpSettings.qml @@ -97,6 +97,7 @@ PageType { clickedFunction: function() { col.copyToClipBoard(descriptionText) + PageController.showNotificationMessage(qsTr("Copied")) } } @@ -113,6 +114,7 @@ PageType { clickedFunction: function() { col.copyToClipBoard(descriptionText) + PageController.showNotificationMessage(qsTr("Copied")) } } @@ -129,6 +131,7 @@ PageType { clickedFunction: function() { col.copyToClipBoard(descriptionText) + PageController.showNotificationMessage(qsTr("Copied")) } } @@ -145,6 +148,7 @@ PageType { clickedFunction: function() { col.copyToClipBoard(descriptionText) + PageController.showNotificationMessage(qsTr("Copied")) } } @@ -194,7 +198,9 @@ PageType { readonly property string macosFirstLink: "macFUSE" readonly property string macosSecondLink: "SSHFS" - onLinkActivated: Qt.openUrlExternally(link) + onLinkActivated: function(link) { + Qt.openUrlExternally(link) + } textFormat: Text.RichText text: { var str = qsTr("In order to mount remote SFTP folder as local drive, perform following steps:
") @@ -210,6 +216,8 @@ PageType { return str } + + } BasicButtonType { diff --git a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml index 40e1b339..080f5ee6 100644 --- a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml +++ b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml @@ -79,6 +79,7 @@ PageType { clickedFunction: function() { content.copyToClipBoard(descriptionText) + PageController.showNotificationMessage(qsTr("Copied")) } } diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index a43e9e5e..5cd0d095 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -116,7 +116,7 @@ PageType { headerText: qsTr("Mode") - listView: ListViewType { + listView: ListViewWithRadioButtonType { rootWidth: root.width model: root.routeModesModel diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index f2ed4607..065bda12 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -166,7 +166,7 @@ PageType { descriptionText: accessTypeSelector.currentIndex === 0 ? qsTr("Server and service") : qsTr("Server") headerText: qsTr("Server") - listView: ListViewType { + listView: ListViewWithLabelsType { rootWidth: root.width dividerVisible: true @@ -255,9 +255,8 @@ PageType { wrapMode: Text.WordWrap } - ListViewType { + ListViewWithRadioButtonType { rootWidth: root.width - dividerVisible: true imageSource: "qrc:/images/controls/check.svg" @@ -274,7 +273,7 @@ PageType { currentIndex: 0 - clickedFunction: function () { + clickedFunction: function() { handler() protocolSelector.visible = false @@ -338,11 +337,10 @@ PageType { descriptionText: qsTr("Connection format") headerText: qsTr("Connection format") - listView: ListViewType { + listView: ListViewWithRadioButtonType { id: exportTypeListView rootWidth: root.width - dividerVisible: true imageSource: "qrc:/images/controls/chevron-right.svg" From a40f365a543d0545ab31217fe8fef44b223dea20 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 16 Aug 2023 23:48:25 +0500 Subject: [PATCH 063/278] "added display of busy server package manager on the PageSetupWizardInstalling" --- client/ui/controllers/installController.cpp | 3 +++ client/ui/controllers/installController.h | 2 ++ client/ui/qml/Pages2/PageDeinstalling.qml | 2 +- .../qml/Pages2/PageSetupWizardInstalling.qml | 23 ++++++++++++++++--- 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 7e71aef8..06a5c554 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -85,6 +85,7 @@ void InstallController::install(DockerContainer container, int port, TransportPr void InstallController::installServer(DockerContainer container, QJsonObject &config) { ServerController serverController(m_settings); + connect(&serverController, &ServerController::serverIsBusy, this, &InstallController::serverIsBusy); QMap installedContainers; ErrorCode errorCode = @@ -138,6 +139,7 @@ void InstallController::installContainer(DockerContainer container, QJsonObject qvariant_cast(m_serversModel->data(serverIndex, ServersModel::Roles::CredentialsRole)); ServerController serverController(m_settings); + connect(&serverController, &ServerController::serverIsBusy, this, &InstallController::serverIsBusy); QMap installedContainers; ErrorCode errorCode = serverController.getAlreadyInstalledContainers(serverCredentials, installedContainers); @@ -239,6 +241,7 @@ void InstallController::updateContainer(QJsonObject config) qvariant_cast(m_containersModel->data(modelIndex, ContainersModel::Roles::ConfigRole)); ServerController serverController(m_settings); + connect(&serverController, &ServerController::serverIsBusy, this, &InstallController::serverIsBusy); auto errorCode = serverController.updateContainer(serverCredentials, container, oldContainerConfig, config); if (errorCode == ErrorCode::NoError) { diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index b74835d9..63d4c714 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -60,6 +60,8 @@ signals: void passphraseRequestStarted(); void passphraseRequestFinished(); + void serverIsBusy(const bool isBusy); + private: void installServer(DockerContainer container, QJsonObject &config); void installContainer(DockerContainer container, QJsonObject &config); diff --git a/client/ui/qml/Pages2/PageDeinstalling.qml b/client/ui/qml/Pages2/PageDeinstalling.qml index eff58055..243b1205 100644 --- a/client/ui/qml/Pages2/PageDeinstalling.qml +++ b/client/ui/qml/Pages2/PageDeinstalling.qml @@ -75,7 +75,7 @@ PageType { repeat: true running: true onTriggered: { - progressBar.value += 0.001 + progressBar.value += 0.003 } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index 10daa0be..20c589ee 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -17,6 +17,9 @@ PageType { Component.onCompleted: PageController.enableTabBar(false) Component.onDestruction: PageController.enableTabBar(true) + property bool isTimerRunning: true + property string progressBarText: qsTr("Usually it takes no more than 5 minutes") + Connections { target: InstallController @@ -57,6 +60,18 @@ PageType { PageController.showErrorMessage(qsTr("The server has already been added to the application")) } + + function onServerIsBusy(isBusy) { + if (isBusy) { + root.progressBarText = qsTr("Amnesia has detected that your server is currently ") + + qsTr("busy installing other software. Amnesia installation ") + + qsTr("will pause until the server finishes installing other software") + root.isTimerRunning = false + } else { + root.progressBarText = qsTr("Usually it takes no more than 5 minutes") + root.isTimerRunning = true + } + } } SortFilterProxyModel { @@ -122,18 +137,20 @@ PageType { interval: 300 repeat: true - running: true + running: root.isTimerRunning onTriggered: { - progressBar.value += 0.001 + progressBar.value += 0.003 } } } ParagraphTextType { + id: progressText + Layout.fillWidth: true Layout.topMargin: 8 - text: qsTr("Usually it takes no more than 5 minutes") + text: root.progressBarText } } } From ddaa5b784d89946f3b3b9523920c579c0220cd68 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 18 Aug 2023 14:14:45 +0500 Subject: [PATCH 064/278] minor ui fixes --- .../ui/qml/Controls2/LabelWithButtonType.qml | 27 +++++++++++++------ .../ui/qml/Controls2/VerticalRadioButton.qml | 1 + client/ui/qml/Pages2/PageSettingsAbout.qml | 8 +++--- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml index 3aca1d57..82aef55a 100644 --- a/client/ui/qml/Controls2/LabelWithButtonType.qml +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -17,7 +17,9 @@ Item { property string textColor: "#d7d8db" property string descriptionColor: "#878B91" - property string rightImageColor: "#878B91" + property real textOpacity: 1.0 + + property string rightImageColor: "#d7d8db" property bool descriptionOnTop: false @@ -67,6 +69,8 @@ Item { text: root.text color: root.descriptionOnTop ? root.descriptionColor : root.textColor + opacity: root.textOpacity + Layout.fillWidth: true lineHeight: root.descriptionOnTop ? parent.descriptionTextLineHeight : parent.textLineHeight @@ -75,14 +79,20 @@ Item { horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter + + Behavior on opacity { + PropertyAnimation { duration: 200 } + } } CaptionTextType { id: description - color: root.descriptionOnTop ? root.textColor : root.descriptionColor text: root.descriptionText + color: root.descriptionOnTop ? root.textColor : root.descriptionColor + + opacity: root.textOpacity visible: root.descriptionText !== "" @@ -94,6 +104,10 @@ Item { horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter + + Behavior on opacity { + PropertyAnimation { duration: 200 } + } } } @@ -141,9 +155,8 @@ Item { rightImageBackground.color = rightImage.hoveredColor } else if (leftImageSource) { leftImageBackground.color = rightImage.hoveredColor - } else { - background.color = rightImage.hoveredColor } + root.textOpacity = 0.8 } onExited: { @@ -151,9 +164,8 @@ Item { rightImageBackground.color = rightImage.defaultColor } else if (leftImageSource) { leftImageBackground.color = rightImage.defaultColor - } else { - background.color = rightImage.defaultColor } + root.textOpacity = 1 } onPressedChanged: { @@ -161,9 +173,8 @@ Item { rightImageBackground.color = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor } else if (leftImageSource) { leftImageBackground.color = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor - } else { - background.color = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor } + root.textOpacity = 0.7 } onClicked: { diff --git a/client/ui/qml/Controls2/VerticalRadioButton.qml b/client/ui/qml/Controls2/VerticalRadioButton.qml index f44fa751..8229c959 100644 --- a/client/ui/qml/Controls2/VerticalRadioButton.qml +++ b/client/ui/qml/Controls2/VerticalRadioButton.qml @@ -67,6 +67,7 @@ RadioButton { width: 24 height: 24 } + Image { source: { if (showImage) { diff --git a/client/ui/qml/Pages2/PageSettingsAbout.qml b/client/ui/qml/Pages2/PageSettingsAbout.qml index 1875f085..9fc23c15 100644 --- a/client/ui/qml/Pages2/PageSettingsAbout.qml +++ b/client/ui/qml/Pages2/PageSettingsAbout.qml @@ -81,7 +81,8 @@ And if you don't like the app, all the more support it - the donation will be us text: qsTr("Card on Patreon") - onClicked: { + clickedFunction: function() { + Qt.openUrlExternally(qsTr("https://www.patreon.com/amneziavpn")) } } @@ -121,7 +122,7 @@ And if you don't like the app, all the more support it - the donation will be us leftImageSource: "qrc:/images/controls/telegram.svg" clickedFunction: function() { - Qt.openUrlExternally("https://t.me/amnezia_vpn_dev") + Qt.openUrlExternally(qsTr("https://t.me/amnezia_vpn_dev")) } } @@ -147,7 +148,7 @@ And if you don't like the app, all the more support it - the donation will be us leftImageSource: "qrc:/images/controls/github.svg" clickedFunction: function() { - Qt.openUrlExternally("https://github.com/amnezia-vpn/amnezia-client") + Qt.openUrlExternally(qsTr("https://github.com/amnezia-vpn/amnezia-client")) } } @@ -160,6 +161,7 @@ And if you don't like the app, all the more support it - the donation will be us leftImageSource: "qrc:/images/controls/amnezia.svg" clickedFunction: function() { + Qt.openUrlExternally(qsTr("amnezia.org/")) } } From 0060f57b634236aff626f708cb239ba82a97b6ab Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sat, 19 Aug 2023 13:12:54 +0500 Subject: [PATCH 065/278] fixed selection of connection type on PageShare when changing protocol --- client/ui/qml/Pages2/PageShare.qml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 065bda12..28f5ca06 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -338,14 +338,18 @@ PageType { headerText: qsTr("Connection format") listView: ListViewWithRadioButtonType { - id: exportTypeListView + onCurrentIndexChanged: { + console.log(currentIndex) + exportTypeSelector.currentIndex = currentIndex + exportTypeSelector.text = selectedText + } rootWidth: root.width - imageSource: "qrc:/images/controls/chevron-right.svg" + imageSource: "qrc:/images/controls/check.svg" model: root.connectionTypesModel - currentIndex: exportTypeSelector.currentIndex + currentIndex: 0 clickedFunction: function() { exportTypeSelector.text = selectedText From b5e1c78461a9cf52e8743a17e7e675a35a3831f7 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sun, 20 Aug 2023 13:36:54 +0500 Subject: [PATCH 066/278] minor ui fixes --- client/containers/containers_defs.cpp | 29 ++++- client/containers/containers_defs.h | 105 +++++++++--------- client/ui/controllers/installController.cpp | 15 +++ client/ui/controllers/installController.h | 2 + client/ui/models/containers_model.cpp | 8 +- client/ui/models/containers_model.h | 3 +- .../Pages2/PageServiceTorWebsiteSettings.qml | 2 +- client/ui/qml/Pages2/PageSettingsAbout.qml | 2 +- .../qml/Pages2/PageSettingsSplitTunneling.qml | 2 +- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 26 ++++- .../PageSetupWizardProtocolSettings.qml | 4 +- 11 files changed, 132 insertions(+), 66 deletions(-) diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index 27f6f1dd..fc4570f4 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -81,7 +81,7 @@ QMap ContainerProps::containerHumanNames() { return { { DockerContainer::None, "Not installed" }, { DockerContainer::OpenVpn, "OpenVPN" }, - { DockerContainer::ShadowSocks, "OpenVPN over ShadowSocks" }, + { DockerContainer::ShadowSocks, "ShadowSocks" }, { DockerContainer::Cloak, "OpenVPN over Cloak" }, { DockerContainer::WireGuard, "WireGuard" }, { DockerContainer::Ipsec, QObject::tr("IPsec") }, @@ -93,6 +93,33 @@ QMap ContainerProps::containerHumanNames() } QMap ContainerProps::containerDescriptions() +{ + return { { DockerContainer::OpenVpn, + QObject::tr("OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its " + "own security protocol with SSL/TLS for key exchange.") }, + { DockerContainer::ShadowSocks, + QObject::tr("ShadowSocks - masks VPN traffic, making it similar to normal web traffic, but is " + "recognised by analysis systems in some highly censored regions.") }, + { DockerContainer::Cloak, + QObject::tr("OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against " + "active-probbing detection. Ideal for bypassing blocking in regions with the highest levels " + "of censorship.") }, + { DockerContainer::WireGuard, + QObject::tr("WireGuard - New popular VPN protocol with high performance, high speed and low power " + "consumption. Recommended for regions with low levels of censorship.") }, + { DockerContainer::Ipsec, + QObject::tr("IKEv2 - Modern stable protocol, a bit faster than others, restores connection after " + "signal loss. It has native support on the latest versions of Android and iOS.") }, + + { DockerContainer::TorWebSite, QObject::tr("Deploy a WordPress site on the Tor network in two clicks.") }, + { DockerContainer::Dns, + QObject::tr("Replace the current DNS server with your own. This will increase your privacy level.") }, + //{DockerContainer::FileShare, QObject::tr("SMB file sharing service - is Window file sharing protocol")}, + { DockerContainer::Sftp, + QObject::tr("Creates a file vault on your server to securely store and transfer files.") } }; +} + +QMap ContainerProps::containerDetailedDescriptions() { return { { DockerContainer::OpenVpn, QObject::tr("OpenVPN container") }, { DockerContainer::ShadowSocks, QObject::tr("Container with OpenVpn and ShadowSocks") }, diff --git a/client/containers/containers_defs.h b/client/containers/containers_defs.h index d4a35d26..a84d997c 100644 --- a/client/containers/containers_defs.h +++ b/client/containers/containers_defs.h @@ -8,72 +8,69 @@ using namespace amnezia; -namespace amnezia { - -namespace ContainerEnumNS { -Q_NAMESPACE -enum DockerContainer { - None = 0, - OpenVpn, - ShadowSocks, - Cloak, - WireGuard, - Ipsec, - - //non-vpn - TorWebSite, - Dns, - //FileShare, - Sftp -}; -Q_ENUM_NS(DockerContainer) -} // namespace ContainerEnumNS - -using namespace ContainerEnumNS; -using namespace ProtocolEnumNS; - -class ContainerProps : public QObject +namespace amnezia { - Q_OBJECT -public: - Q_INVOKABLE static amnezia::DockerContainer containerFromString(const QString &container); - Q_INVOKABLE static QString containerToString(amnezia::DockerContainer container); - Q_INVOKABLE static QString containerTypeToString(amnezia::DockerContainer c); + namespace ContainerEnumNS + { + Q_NAMESPACE + enum DockerContainer { + None = 0, + OpenVpn, + ShadowSocks, + Cloak, + WireGuard, + Ipsec, - Q_INVOKABLE static QList allContainers(); + // non-vpn + TorWebSite, + Dns, + // FileShare, + Sftp + }; + Q_ENUM_NS(DockerContainer) + } // namespace ContainerEnumNS - Q_INVOKABLE static QMap containerHumanNames(); - Q_INVOKABLE static QMap containerDescriptions(); + using namespace ContainerEnumNS; + using namespace ProtocolEnumNS; - // these protocols will be displayed in container settings - Q_INVOKABLE static QVector protocolsForContainer(amnezia::DockerContainer container); + class ContainerProps : public QObject + { + Q_OBJECT - Q_INVOKABLE static amnezia::ServiceType containerService(amnezia::DockerContainer c); + public: + Q_INVOKABLE static amnezia::DockerContainer containerFromString(const QString &container); + Q_INVOKABLE static QString containerToString(amnezia::DockerContainer container); + Q_INVOKABLE static QString containerTypeToString(amnezia::DockerContainer c); - // binding between Docker container and main protocol of given container - // it may be changed fot future containers :) - Q_INVOKABLE static amnezia::Proto defaultProtocol(amnezia::DockerContainer c); + Q_INVOKABLE static QList allContainers(); - Q_INVOKABLE static bool isSupportedByCurrentPlatform(amnezia::DockerContainer c); - Q_INVOKABLE static QStringList fixedPortsForContainer(amnezia::DockerContainer c); + Q_INVOKABLE static QMap containerHumanNames(); + Q_INVOKABLE static QMap containerDescriptions(); + Q_INVOKABLE static QMap containerDetailedDescriptions(); - static bool isEasySetupContainer(amnezia::DockerContainer container); - static QString easySetupHeader(amnezia::DockerContainer container); - static QString easySetupDescription(amnezia::DockerContainer container); -}; + // these protocols will be displayed in container settings + Q_INVOKABLE static QVector protocolsForContainer(amnezia::DockerContainer container); + Q_INVOKABLE static amnezia::ServiceType containerService(amnezia::DockerContainer c); + // binding between Docker container and main protocol of given container + // it may be changed fot future containers :) + Q_INVOKABLE static amnezia::Proto defaultProtocol(amnezia::DockerContainer c); -static void declareQmlContainerEnum() { - qmlRegisterUncreatableMetaObject( - ContainerEnumNS::staticMetaObject, - "ContainerEnum", - 1, 0, - "ContainerEnum", - "Error: only enums" - ); -} + Q_INVOKABLE static bool isSupportedByCurrentPlatform(amnezia::DockerContainer c); + Q_INVOKABLE static QStringList fixedPortsForContainer(amnezia::DockerContainer c); + + static bool isEasySetupContainer(amnezia::DockerContainer container); + static QString easySetupHeader(amnezia::DockerContainer container); + static QString easySetupDescription(amnezia::DockerContainer container); + }; + + static void declareQmlContainerEnum() + { + qmlRegisterUncreatableMetaObject(ContainerEnumNS::staticMetaObject, "ContainerEnum", 1, 0, "ContainerEnum", + "Error: only enums"); + } } // namespace amnezia diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 06a5c554..18588aef 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -446,3 +446,18 @@ void InstallController::setEncryptedPassphrase(QString passphrase) m_privateKeyPassphrase = passphrase; emit passphraseRequestFinished(); } + +void InstallController::addEmptyServer() +{ + QJsonObject server; + server.insert(config_key::hostName, m_currentlyInstalledServerCredentials.hostName); + server.insert(config_key::userName, m_currentlyInstalledServerCredentials.userName); + server.insert(config_key::password, m_currentlyInstalledServerCredentials.secretData); + server.insert(config_key::port, m_currentlyInstalledServerCredentials.port); + server.insert(config_key::description, m_settings->nextAvailableServerName()); + + m_serversModel->addServer(server); + m_serversModel->setDefaultServerIndex(m_serversModel->getServersCount() - 1); + + emit installServerFinished(tr("Server added successfully")); +} diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index 63d4c714..4060c97c 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -41,6 +41,8 @@ public slots: void setEncryptedPassphrase(QString passphrase); + void addEmptyServer(); + signals: void installContainerFinished(const QString &finishMessage, bool isServiceInstall); void installServerFinished(const QString &finishMessage); diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index dd1d8d4e..0a07895c 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -24,7 +24,7 @@ bool ContainersModel::setData(const QModelIndex &index, const QVariant &value, i switch (role) { case NameRole: // return ContainerProps::containerHumanNames().value(container); - case DescRole: + case DescriptionRole: // return ContainerProps::containerDescriptions().value(container); case ConfigRole: { m_settings->setContainerConfig(m_currentlyProcessedServerIndex, container, value.toJsonObject()); @@ -62,7 +62,8 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const switch (role) { case NameRole: return ContainerProps::containerHumanNames().value(container); - case DescRole: return ContainerProps::containerDescriptions().value(container); + case DescriptionRole: return ContainerProps::containerDescriptions().value(container); + case DetailedDescriptionRole: return ContainerProps::containerDetailedDescriptions().value(container); case ConfigRole: { if (container == DockerContainer::None) { return QJsonObject(); @@ -220,7 +221,8 @@ QHash ContainersModel::roleNames() const { QHash roles; roles[NameRole] = "name"; - roles[DescRole] = "description"; + roles[DescriptionRole] = "description"; + roles[DetailedDescriptionRole] = "detailedDescription"; roles[ServiceTypeRole] = "serviceType"; roles[DockerContainerRole] = "dockerContainer"; roles[ConfigRole] = "config"; diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index 8978315c..cde77526 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -17,7 +17,8 @@ public: enum Roles { NameRole = Qt::UserRole + 1, - DescRole, + DescriptionRole, + DetailedDescriptionRole, ServiceTypeRole, ConfigRole, DockerContainerRole, diff --git a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml index 080f5ee6..38490375 100644 --- a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml +++ b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml @@ -138,7 +138,7 @@ PageType { text: qsTr("Remove website") onClicked: { - questionDrawer.headerText = qsTr("Some description") + questionDrawer.headerText = qsTr("The site with all data will be removed from the tor network.") questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.noButtonText = qsTr("Cancel") diff --git a/client/ui/qml/Pages2/PageSettingsAbout.qml b/client/ui/qml/Pages2/PageSettingsAbout.qml index 9fc23c15..67dada43 100644 --- a/client/ui/qml/Pages2/PageSettingsAbout.qml +++ b/client/ui/qml/Pages2/PageSettingsAbout.qml @@ -81,7 +81,7 @@ And if you don't like the app, all the more support it - the donation will be us text: qsTr("Card on Patreon") - clickedFunction: function() { + onClicked: function() { Qt.openUrlExternally(qsTr("https://www.patreon.com/amneziavpn")) } } diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index 5cd0d095..71f2ba29 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -43,7 +43,7 @@ PageType { QtObject { id: onlyForwardSites - property string name: qsTr("Addresses from the list should always open via VPN") + property string name: qsTr("Only the addresses in the list must be opened via VPN") property int type: routeMode.onlyForwardSites } QtObject { diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index ac1c3a44..f2600f65 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -126,6 +126,8 @@ PageType { } } + DividerType {} + CardType { implicitWidth: parent.width @@ -143,14 +145,14 @@ PageType { id: continueButton implicitWidth: parent.width - anchors.bottomMargin: 24 + anchors.topMargin: 24 text: qsTr("Continue") onClicked: function() { if (root.isEasySetup) { ContainersModel.setCurrentlyProcessedContainerIndex(containers.dockerContainer) - goToPage(PageEnum.PageSetupWizardInstalling); + goToPage(PageEnum.PageSetupWizardInstalling) InstallController.install(containers.dockerContainer, containers.containerDefaultPort, containers.containerDefaultTransportProto) @@ -159,6 +161,26 @@ PageType { } } } + + BasicButtonType { + implicitWidth: parent.width + anchors.topMargin: 8 + anchors.bottomMargin: 24 + + 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("Set up later") + + onClicked: function() { + goToPage(PageEnum.PageSetupWizardInstalling) + InstallController.addEmptyServer() + } + } } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index f5f07609..2f5ed569 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -73,7 +73,7 @@ PageType { Layout.fillWidth: true headerText: qsTr("Installing ") + name - descriptionText: qsTr("protocol description") + descriptionText: description } BasicButtonType { @@ -159,7 +159,7 @@ PageType { font.weight: Font.Medium font.family: "PT Root UI VF" - text: qsTr("detailed protocol description") + text: detailedDescription wrapMode: Text.WordWrap From 420c616e9d875890449f07731cc7d8e007c6374c Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sun, 20 Aug 2023 13:43:27 +0500 Subject: [PATCH 067/278] added authResultReceiver to android.cmake --- client/cmake/android.cmake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/cmake/android.cmake b/client/cmake/android.cmake index dd37e0a6..9440ad10 100644 --- a/client/cmake/android.cmake +++ b/client/cmake/android.cmake @@ -10,6 +10,7 @@ set(HEADERS ${HEADERS} ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_notificationhandler.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/androidutils.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/androidvpnactivity.h + ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.h ${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.h ) @@ -18,6 +19,7 @@ set(SOURCES ${SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_notificationhandler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/androidutils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/androidvpnactivity.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.cpp ${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.cpp ) From f7926847ac9139bb23bf77a185c73d99cf1764c4 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 22 Aug 2023 14:37:29 +0500 Subject: [PATCH 068/278] minor ui fixes --- client/containers/containers_defs.cpp | 10 ++++++++ client/containers/containers_defs.h | 2 ++ client/ui/controllers/exportController.cpp | 25 +++---------------- client/ui/controllers/exportController.h | 2 +- client/ui/models/containers_model.cpp | 2 ++ client/ui/models/containers_model.h | 3 ++- .../qml/Components/ShareConnectionDrawer.qml | 12 ++++++++- client/ui/qml/Pages2/PageSettingsAbout.qml | 2 +- .../ui/qml/Pages2/PageSettingsConnection.qml | 1 - .../ui/qml/Pages2/PageSetupWizardQrReader.qml | 4 --- client/ui/qml/Pages2/PageShare.qml | 21 +++++++++++++--- 11 files changed, 50 insertions(+), 34 deletions(-) diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index fc4570f4..98b2d0da 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -249,3 +249,13 @@ QString ContainerProps::easySetupDescription(DockerContainer container) default: return ""; } } + +bool ContainerProps::isShareable(DockerContainer container) +{ + switch (container) { + case DockerContainer::TorWebSite: return false; + case DockerContainer::Dns: return false; + case DockerContainer::Sftp: return false; + default: return true; + } +} diff --git a/client/containers/containers_defs.h b/client/containers/containers_defs.h index a84d997c..24782407 100644 --- a/client/containers/containers_defs.h +++ b/client/containers/containers_defs.h @@ -64,6 +64,8 @@ namespace amnezia static bool isEasySetupContainer(amnezia::DockerContainer container); static QString easySetupHeader(amnezia::DockerContainer container); static QString easySetupDescription(amnezia::DockerContainer container); + + static bool isShareable(amnezia::DockerContainer container); }; static void declareQmlContainerEnum() diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index 8fbd69f1..abe9fda2 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -12,6 +12,7 @@ #include "configurators/openvpn_configurator.h" #include "configurators/wireguard_configurator.h" #include "core/errorstrings.h" +#include "utilities.h" #ifdef Q_OS_ANDROID #include "platforms/android/android_controller.h" #include "platforms/android/androidutils.h" @@ -201,7 +202,7 @@ QList ExportController::getQrCodes() return m_qrCodes; } -void ExportController::saveFile() +void ExportController::saveFile(const QString &fileExtension, const QString &caption, const QString &fileName) { #if defined Q_OS_IOS // ext.replace("*", ""); @@ -229,27 +230,7 @@ void ExportController::saveFile() return; #endif - 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_config.toUtf8()); - save.close(); - - QFileInfo fi(fileName.toLocalFile()); - QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); + Utils::saveFile(fileExtension, caption, fileName, m_config); } QList ExportController::generateQrCodeImageSeries(const QByteArray &data) diff --git a/client/ui/controllers/exportController.h b/client/ui/controllers/exportController.h index ce952096..913dfc3a 100644 --- a/client/ui/controllers/exportController.h +++ b/client/ui/controllers/exportController.h @@ -35,7 +35,7 @@ public slots: QString getConfig(); QList getQrCodes(); - void saveFile(); + void saveFile(const QString &fileExtension, const QString &caption, const QString &fileName); signals: void generateConfig(int type); diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index 0a07895c..67847727 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -79,6 +79,7 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const case IsCurrentlyProcessedRole: return container == static_cast(m_currentlyProcessedContainerIndex); case IsDefaultRole: return container == m_defaultContainerIndex; case IsSupportedRole: return ContainerProps::isSupportedByCurrentPlatform(container); + case IsShareableRole: return ContainerProps::isShareable(container); } return QVariant(); @@ -235,5 +236,6 @@ QHash ContainersModel::roleNames() const roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed"; roles[IsDefaultRole] = "isDefault"; roles[IsSupportedRole] = "isSupported"; + roles[IsShareableRole] = "isShareable"; return roles; } diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index cde77526..547eea83 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -30,7 +30,8 @@ public: IsInstalledRole, IsCurrentlyProcessedRole, IsDefaultRole, - IsSupportedRole + IsSupportedRole, + IsShareableRole }; int rowCount(const QModelIndex &parent = QModelIndex()) const override; diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 7ad724ea..368d503a 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -19,9 +19,19 @@ DrawerType { property alias configContentHeaderText: configContentHeader.headerText property alias contentVisible: content.visible + property string configExtension: ".vpn" + property string configCaption: qsTr("Save AmneziaVPN config") + property string configFileName: "amnezia_config.vpn" + width: parent.width height: parent.height * 0.9 + onClosed: { + configExtension = ".vpn" + configCaption = qsTr("Save AmneziaVPN config") + configFileName = "amnezia_config.vpn" + } + Item { anchors.fill: parent @@ -58,7 +68,7 @@ DrawerType { imageSource: "qrc:/images/controls/share-2.svg" onClicked: { - ExportController.saveFile() + ExportController.saveFile(configExtension, configCaption, configFileName) } } diff --git a/client/ui/qml/Pages2/PageSettingsAbout.qml b/client/ui/qml/Pages2/PageSettingsAbout.qml index 67dada43..1a6f7e80 100644 --- a/client/ui/qml/Pages2/PageSettingsAbout.qml +++ b/client/ui/qml/Pages2/PageSettingsAbout.qml @@ -161,7 +161,7 @@ And if you don't like the app, all the more support it - the donation will be us leftImageSource: "qrc:/images/controls/amnezia.svg" clickedFunction: function() { - Qt.openUrlExternally(qsTr("amnezia.org/")) + Qt.openUrlExternally(qsTr("https://amnezia.org")) } } diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml index 6465e40e..0765f7d2 100644 --- a/client/ui/qml/Pages2/PageSettingsConnection.qml +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -67,7 +67,6 @@ PageType { Layout.rightMargin: 16 text: qsTr("Use AmneziaDNS if installed on the server") - descriptionText: qsTr("Internal IP address 172.29.172.254") checked: SettingsController.isAmneziaDnsEnabled() onCheckedChanged: { diff --git a/client/ui/qml/Pages2/PageSetupWizardQrReader.qml b/client/ui/qml/Pages2/PageSetupWizardQrReader.qml index 695175fd..1fa71592 100644 --- a/client/ui/qml/Pages2/PageSetupWizardQrReader.qml +++ b/client/ui/qml/Pages2/PageSetupWizardQrReader.qml @@ -73,10 +73,6 @@ PageType { } Component.onCompleted: { - console.log(qrCodeRectange.x) - console.log(qrCodeRectange.y) - console.log(qrCodeRectange.width) - qrCodeReader.setCameraSize(Qt.rect(qrCodeRectange.x, qrCodeRectange.y, qrCodeRectange.width, diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 28f5ca06..120e04fc 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -41,8 +41,20 @@ PageType { } break; } - case PageShare.ConfigType.OpenVpn: ExportController.generateOpenVpnConfig(); break; - case PageShare.ConfigType.WireGuard: ExportController.generateWireGuardConfig(); break; + case PageShare.ConfigType.OpenVpn: { + ExportController.generateOpenVpnConfig(); + shareConnectionDrawer.configCaption = qsTr("Save OpenVPN config") + shareConnectionDrawer.configExtension = ".ovpn" + shareConnectionDrawer.configFileName = "amnezia_for_openvpn" + break; + } + case PageShare.ConfigType.WireGuard: { + ExportController.generateWireGuardConfig(); + shareConnectionDrawer.configCaption = qsTr("Save WireGuard config") + shareConnectionDrawer.configExtension = ".conf" + shareConnectionDrawer.configFileName = "amnezia_for_wireguard" + break; + } } PageController.showBusyIndicator(false) @@ -267,6 +279,10 @@ PageType { ValueFilter { roleName: "isInstalled" value: true + }, + ValueFilter { + roleName: "isShareable" + value: true } ] } @@ -339,7 +355,6 @@ PageType { listView: ListViewWithRadioButtonType { onCurrentIndexChanged: { - console.log(currentIndex) exportTypeSelector.currentIndex = currentIndex exportTypeSelector.text = selectedText } From 23ad006187b1bf6f3e073f6ae0e3f1f022f9af0c Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 23 Aug 2023 00:20:59 +0500 Subject: [PATCH 069/278] removed Widgets from service part --- client/amnezia_application.cpp | 10 -- client/fileUtilites.cpp | 47 ++++++ client/fileUtilites.h | 19 +++ client/ui/controllers/exportController.cpp | 5 +- client/ui/controllers/importController.cpp | 8 +- client/ui/controllers/installController.cpp | 1 + client/ui/controllers/settingsController.cpp | 10 +- client/ui/controllers/sitesController.cpp | 9 +- client/utilities.cpp | 154 +++++++------------ client/utilities.h | 73 +++++---- service/server/CMakeLists.txt | 4 +- 11 files changed, 178 insertions(+), 162 deletions(-) create mode 100644 client/fileUtilites.cpp create mode 100644 client/fileUtilites.h diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index d6d346ab..ba4499a1 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -10,7 +10,6 @@ #include #include -#include "core/servercontroller.h" #include "logger.h" #include "version.h" @@ -20,7 +19,6 @@ #endif #include "protocols/qml_register_protocols.h" -#include "ui/pages.h" #if defined(Q_OS_IOS) #include "platforms/ios/QtAppDelegate-C-Interface.h" @@ -81,8 +79,6 @@ void AmneziaApplication::init() m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance()); - // - m_configurator = std::shared_ptr(new VpnConfigurator(m_settings, this)); m_vpnConnection.reset(new VpnConnection(m_settings, m_configurator)); m_vpnConnection->moveToThread(&m_vpnConnectionThread); @@ -125,14 +121,8 @@ void AmneziaApplication::init() connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(), &ConnectionController::closeConnection); - // - m_engine->load(url); - // if (m_engine->rootObjects().size() > 0) { - // m_uiLogic->setQmlRoot(m_engine->rootObjects().at(0)); - // } - if (m_settings->isSaveLogs()) { if (!Logger::init()) { qWarning() << "Initialization of debug subsystem failed"; diff --git a/client/fileUtilites.cpp b/client/fileUtilites.cpp new file mode 100644 index 00000000..87fa7aed --- /dev/null +++ b/client/fileUtilites.cpp @@ -0,0 +1,47 @@ +#include "fileUtilites.h" + +#include +#include + +void FileUtilites::saveFile(const QString &fileExtension, const QString &caption, const QString &fileName, + const QString &data) +{ + QString docDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); + QUrl fileUrl = QFileDialog::getSaveFileUrl(nullptr, caption, QUrl::fromLocalFile(docDir + "/" + fileName), + "*" + fileExtension); + if (fileUrl.isEmpty()) + return; + if (!fileUrl.toString().endsWith(fileExtension)) { + fileUrl = QUrl(fileUrl.toString() + fileExtension); + } + if (fileUrl.isEmpty()) + return; + + QFile save(fileUrl.toLocalFile()); + + // todo check if save successful + save.open(QIODevice::WriteOnly); + save.write(data.toUtf8()); + save.close(); + + QFileInfo fi(fileUrl.toLocalFile()); + QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); +} + +QString FileUtilites::getFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, + QString *selectedFilter, QFileDialog::Options options) +{ + QString fileName = QFileDialog::getOpenFileName(parent, caption, dir, filter, selectedFilter, options); + +#ifdef Q_OS_ANDROID + // patch for files containing spaces etc + const QString sep { "raw%3A%2F" }; + if (fileName.startsWith("content://") && fileName.contains(sep)) { + QString contentUrl = fileName.split(sep).at(0); + QString rawUrl = fileName.split(sep).at(1); + rawUrl.replace(" ", "%20"); + fileName = contentUrl + sep + rawUrl; + } +#endif + return fileName; +} diff --git a/client/fileUtilites.h b/client/fileUtilites.h new file mode 100644 index 00000000..8cf4807c --- /dev/null +++ b/client/fileUtilites.h @@ -0,0 +1,19 @@ +#ifndef FILEUTILITES_H +#define FILEUTILITES_H + +#include + +class FileUtilites : public QObject +{ + Q_OBJECT + +public: + static void saveFile(const QString &fileExtension, const QString &caption, const QString &fileName, + const QString &data); + + static QString getFileName(QWidget *parent = nullptr, const QString &caption = QString(), + const QString &dir = QString(), const QString &filter = QString(), + QString *selectedFilter = nullptr, QFileDialog::Options options = QFileDialog::Options()); +}; + +#endif // FILEUTILITES_H diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index abe9fda2..4cee1dc9 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include @@ -12,7 +11,7 @@ #include "configurators/openvpn_configurator.h" #include "configurators/wireguard_configurator.h" #include "core/errorstrings.h" -#include "utilities.h" +#include "fileUtilites.h" #ifdef Q_OS_ANDROID #include "platforms/android/android_controller.h" #include "platforms/android/androidutils.h" @@ -230,7 +229,7 @@ void ExportController::saveFile(const QString &fileExtension, const QString &cap return; #endif - Utils::saveFile(fileExtension, caption, fileName, m_config); + FileUtilites::saveFile(fileExtension, caption, fileName, m_config); } QList ExportController::generateQrCodeImageSeries(const QByteArray &data) diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 4ff972cc..b5d10abf 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -10,7 +10,7 @@ #include "../../platforms/android/androidutils.h" #include #endif -#include "utilities.h" +#include "fileUtilites.h" namespace { @@ -84,9 +84,9 @@ ImportController::ImportController(const QSharedPointer &serversMo void ImportController::extractConfigFromFile() { - QString fileName = Utils::getFileName(Q_NULLPTR, tr("Open config file"), - QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), - "*.vpn *.ovpn *.conf"); + QString fileName = FileUtilites::getFileName(Q_NULLPTR, tr("Open config file"), + QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), + "*.vpn *.ovpn *.conf"); QFile file(fileName); if (file.open(QIODevice::ReadOnly)) { QString data = file.readAll(); diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 18588aef..7d6ba590 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -7,6 +7,7 @@ #include "core/errorstrings.h" #include "core/servercontroller.h" +#include "fileUtilites.h" #include "utilities.h" namespace diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index 531de14d..b56e48ad 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -2,8 +2,8 @@ #include +#include "fileUtilites.h" #include "logger.h" -#include "utilities.h" #include "version.h" SettingsController::SettingsController(const QSharedPointer &serversModel, @@ -69,7 +69,7 @@ void SettingsController::openLogsFolder() void SettingsController::exportLogsFile() { - Utils::saveFile(".log", tr("Save log"), "AmneziaVPN", Logger::getLogFile()); + FileUtilites::saveFile(".log", tr("Save log"), "AmneziaVPN", Logger::getLogFile()); } void SettingsController::clearLogs() @@ -80,14 +80,14 @@ void SettingsController::clearLogs() void SettingsController::backupAppConfig() { - Utils::saveFile(".backup", tr("Backup application config"), "AmneziaVPN", m_settings->backupAppConfig()); + FileUtilites::saveFile(".backup", tr("Backup application config"), "AmneziaVPN", m_settings->backupAppConfig()); } void SettingsController::restoreAppConfig() { QString fileName = - Utils::getFileName(Q_NULLPTR, tr("Open backup"), - QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.backup"); + FileUtilites::getFileName(Q_NULLPTR, tr("Open backup"), + QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.backup"); if (fileName.isEmpty()) { return; diff --git a/client/ui/controllers/sitesController.cpp b/client/ui/controllers/sitesController.cpp index d8bc99c8..d19f5758 100644 --- a/client/ui/controllers/sitesController.cpp +++ b/client/ui/controllers/sitesController.cpp @@ -3,7 +3,7 @@ #include #include -#include "utilities.h" +#include "fileUtilites.h" SitesController::SitesController(const std::shared_ptr &settings, const QSharedPointer &vpnConnection, @@ -80,8 +80,9 @@ void SitesController::removeSite(int index) void SitesController::importSites(bool replaceExisting) { - QString fileName = Utils::getFileName(Q_NULLPTR, tr("Open sites file"), - QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.json"); + QString fileName = + FileUtilites::getFileName(Q_NULLPTR, tr("Open sites file"), + QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.json"); if (fileName.isEmpty()) { return; @@ -149,7 +150,7 @@ void SitesController::exportSites() QJsonDocument jsonDocument(jsonArray); QByteArray jsonData = jsonDocument.toJson(); - Utils::saveFile(".json", tr("Export sites file"), "sites", jsonData); + FileUtilites::saveFile(".json", tr("Export sites file"), "sites", jsonData); emit finished(tr("Export completed")); } diff --git a/client/utilities.cpp b/client/utilities.cpp index f984e9a8..158bce93 100644 --- a/client/utilities.cpp +++ b/client/utilities.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include #include @@ -10,15 +9,15 @@ #include #include -#include "version.h" #include "utilities.h" +#include "version.h" QString Utils::getRandomString(int len) { const QString possibleCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); QString randomString; - for(int i=0; igenerate() % possibleCharacters.length(); QChar nextChar = possibleCharacters.at(index); randomString.append(nextChar); @@ -31,7 +30,7 @@ QString Utils::systemLogPath() #ifdef Q_OS_WIN QStringList locationList = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); QString primaryLocation = "ProgramData"; - foreach (const QString& location, locationList) { + foreach (const QString &location, locationList) { if (location.contains(primaryLocation)) { return QString("%1/%2/log").arg(location).arg(APPLICATION_NAME); } @@ -42,7 +41,7 @@ QString Utils::systemLogPath() #endif } -bool Utils::initializePath(const QString& path) +bool Utils::initializePath(const QString &path) { QDir dir; if (!dir.mkpath(path)) { @@ -52,13 +51,13 @@ bool Utils::initializePath(const QString& path) return true; } -bool Utils::createEmptyFile(const QString& path) +bool Utils::createEmptyFile(const QString &path) { QFile f(path); return f.open(QIODevice::WriteOnly | QIODevice::Truncate); } -QString Utils::executable(const QString& baseName, bool absPath) +QString Utils::executable(const QString &baseName, bool absPath) { QString ext; #ifdef Q_OS_WIN @@ -71,7 +70,7 @@ QString Utils::executable(const QString& baseName, bool absPath) return QCoreApplication::applicationDirPath() + "/" + fileName; } -QString Utils::usrExecutable(const QString& baseName) +QString Utils::usrExecutable(const QString &baseName) { if (QFileInfo::exists("/usr/sbin/" + baseName)) return ("/usr/sbin/" + baseName); @@ -79,18 +78,22 @@ QString Utils::usrExecutable(const QString& baseName) return ("/usr/bin/" + baseName); } -bool Utils::processIsRunning(const QString& fileName) +bool Utils::processIsRunning(const QString &fileName) { #ifdef Q_OS_WIN QProcess process; process.setReadChannel(QProcess::StandardOutput); process.setProcessChannelMode(QProcess::MergedChannels); - process.start("wmic.exe", QStringList() << "/OUTPUT:STDOUT" << "PROCESS" << "get" << "Caption"); + process.start("wmic.exe", + QStringList() << "/OUTPUT:STDOUT" + << "PROCESS" + << "get" + << "Caption"); process.waitForStarted(); process.waitForFinished(); QString processData(process.readAll()); - QStringList processList = processData.split(QRegularExpression("[\r\n]"),Qt::SkipEmptyParts); - foreach (const QString& rawLine, processList) { + QStringList processList = processData.split(QRegularExpression("[\r\n]"), Qt::SkipEmptyParts); + foreach (const QString &rawLine, processList) { const QString line = rawLine.simplified(); if (line.isEmpty()) { continue; @@ -99,7 +102,6 @@ bool Utils::processIsRunning(const QString& fileName) if (line == fileName) { return true; } - } return false; #elif defined(Q_OS_IOS) @@ -107,7 +109,7 @@ bool Utils::processIsRunning(const QString& fileName) #else QProcess process; process.setProcessChannelMode(QProcess::MergedChannels); - process.start("pgrep", QStringList({fileName})); + process.start("pgrep", QStringList({ fileName })); process.waitForFinished(); if (process.exitStatus() == QProcess::NormalExit) { return (process.readAll().toUInt() > 0); @@ -116,7 +118,7 @@ bool Utils::processIsRunning(const QString& fileName) #endif } -QString Utils::getIPAddress(const QString& host) +QString Utils::getIPAddress(const QString &host) { if (ipAddressRegExp().match(host).hasMatch()) { return host; @@ -130,42 +132,48 @@ QString Utils::getIPAddress(const QString& host) return ""; } -QString Utils::getStringBetween(const QString& s, const QString& a, const QString& b) +QString Utils::getStringBetween(const QString &s, const QString &a, const QString &b) { int ap = s.indexOf(a), bp = s.indexOf(b, ap + a.length()); - if(ap < 0 || bp < 0) + if (ap < 0 || bp < 0) return QString(); ap += a.length(); - if(bp - ap <= 0) + if (bp - ap <= 0) return QString(); return s.mid(ap, bp - ap).trimmed(); } -bool Utils::checkIPv4Format(const QString& ip) +bool Utils::checkIPv4Format(const QString &ip) { - if (ip.isEmpty()) return false; + if (ip.isEmpty()) + return false; int count = ip.count("."); - if(count != 3) return false; + if (count != 3) + return false; QHostAddress addr(ip); - return (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol); + return (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol); } bool Utils::checkIpSubnetFormat(const QString &ip) { - if (!ip.contains("/")) return checkIPv4Format(ip); + if (!ip.contains("/")) + return checkIPv4Format(ip); QStringList parts = ip.split("/"); - if (parts.size() != 2) return false; + if (parts.size() != 2) + return false; bool ok; int subnet = parts.at(1).toInt(&ok); - if (subnet >= 0 && subnet <= 32 && ok) return checkIPv4Format(parts.at(0)); - else return false; + if (subnet >= 0 && subnet <= 32 && ok) + return checkIPv4Format(parts.at(0)); + else + return false; } void Utils::killProcessByName(const QString &name) -{ +{ qDebug().noquote() << "Kill process" << name; #ifdef Q_OS_WIN QProcess::execute("taskkill", QStringList() << "/IM" << name << "/F"); @@ -178,40 +186,39 @@ void Utils::killProcessByName(const QString &name) QString Utils::netMaskFromIpWithSubnet(const QString ip) { - if (!ip.contains("/")) return "255.255.255.255"; + if (!ip.contains("/")) + return "255.255.255.255"; bool ok; int prefix = ip.split("/").at(1).toInt(&ok); - if (!ok) return "255.255.255.255"; + if (!ok) + return "255.255.255.255"; unsigned long mask = (0xFFFFFFFF << (32 - prefix)) & 0xFFFFFFFF; - return QString("%1.%2.%3.%4") - .arg(mask >> 24) - .arg((mask >> 16) & 0xFF) - .arg((mask >> 8) & 0xFF) - .arg( mask & 0xFF); + return QString("%1.%2.%3.%4").arg(mask >> 24).arg((mask >> 16) & 0xFF).arg((mask >> 8) & 0xFF).arg(mask & 0xFF); } QString Utils::ipAddressFromIpWithSubnet(const QString ip) { - if (ip.count(".") != 3) return ""; + if (ip.count(".") != 3) + return ""; return ip.split("/").first(); } QStringList Utils::summarizeRoutes(const QStringList &ips, const QString cidr) { -// QMap -// QHostAddress + // QMap + // QHostAddress -// QMap subnets; // <"a.b", > + // QMap subnets; // <"a.b", > -// for (const QString &ip : ips) { -// if (ip.count(".") != 3) continue; + // for (const QString &ip : ips) { + // if (ip.count(".") != 3) continue; -// const QStringList &parts = ip.split("."); -// subnets[parts.at(0) + "." + parts.at(1)].append(ip); -// } + // const QStringList &parts = ip.split("."); + // subnets[parts.at(0) + "." + parts.at(1)].append(ip); + // } return QStringList(); } @@ -252,58 +259,6 @@ QString Utils::certUtilPath() #endif } -void Utils::saveFile(const QString &fileExtension, - const QString &caption, - const QString &fileName, - const QString &data) -{ - QString docDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); - QUrl fileUrl = QFileDialog::getSaveFileUrl(nullptr, - caption, - QUrl::fromLocalFile(docDir + "/" + fileName), - "*" + fileExtension); - if (fileUrl.isEmpty()) - return; - if (!fileUrl.toString().endsWith(fileExtension)) { - fileUrl = QUrl(fileUrl.toString() + fileExtension); - } - if (fileUrl.isEmpty()) - return; - - QFile save(fileUrl.toLocalFile()); - - //todo check if save successful - save.open(QIODevice::WriteOnly); - save.write(data.toUtf8()); - save.close(); - - QFileInfo fi(fileUrl.toLocalFile()); - QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); -} - -QString Utils::getFileName(QWidget *parent, - const QString &caption, - const QString &dir, - const QString &filter, - QString *selectedFilter, - QFileDialog::Options options) -{ - QString fileName - = QFileDialog::getOpenFileName(parent, caption, dir, filter, selectedFilter, options); - -#ifdef Q_OS_ANDROID - // patch for files containing spaces etc - const QString sep{"raw%3A%2F"}; - if (fileName.startsWith("content://") && fileName.contains(sep)) { - QString contentUrl = fileName.split(sep).at(0); - QString rawUrl = fileName.split(sep).at(1); - rawUrl.replace(" ", "%20"); - fileName = contentUrl + sep + rawUrl; - } -#endif - return fileName; -} - #ifdef Q_OS_WIN // Inspired from http://stackoverflow.com/a/15281070/1529139 // and http://stackoverflow.com/q/40059902/1529139 @@ -315,8 +270,7 @@ bool Utils::signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent) // (otherwise AttachConsole will return ERROR_ACCESS_DENIED) bool consoleDetached = (FreeConsole() != FALSE); - if (AttachConsole(dwProcessId) != FALSE) - { + if (AttachConsole(dwProcessId) != FALSE) { // Add a fake Ctrl-C handler for avoid instant kill is this console // WARNING: do not revert it or current program will be also killed SetConsoleCtrlHandler(nullptr, true); @@ -324,11 +278,9 @@ bool Utils::signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent) FreeConsole(); } - if (consoleDetached) - { + if (consoleDetached) { // Create a new console if previous was deleted by OS - if (AttachConsole(thisConsoleId) == FALSE) - { + if (AttachConsole(thisConsoleId) == FALSE) { int errorCode = GetLastError(); if (errorCode == 31) // 31=ERROR_GEN_FAILURE { diff --git a/client/utilities.h b/client/utilities.h index 191fa5c1..7ef0cd3f 100644 --- a/client/utilities.h +++ b/client/utilities.h @@ -1,45 +1,64 @@ #ifndef UTILITIES_H #define UTILITIES_H -#include #include #include #include #ifdef Q_OS_WIN -#include "Windows.h" + #include "Windows.h" #endif -class Utils : public QObject { +class Utils : public QObject +{ Q_OBJECT public: static QString getRandomString(int len); - static QString executable(const QString& baseName, bool absPath); - static QString usrExecutable(const QString& baseName); + static QString executable(const QString &baseName, bool absPath); + static QString usrExecutable(const QString &baseName); static QString systemLogPath(); - static bool createEmptyFile(const QString& path); - static bool initializePath(const QString& path); + static bool createEmptyFile(const QString &path); + static bool initializePath(const QString &path); - static QString getIPAddress(const QString& host); - static QString getStringBetween(const QString& s, const QString& a, const QString& b); - static bool checkIPv4Format(const QString& ip); - static bool checkIpSubnetFormat(const QString& ip); - static QRegularExpression ipAddressRegExp() { return QRegularExpression("^((25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])(\\.(?!$)|$)){4}$"); } - static QRegularExpression ipAddressPortRegExp() { return QRegularExpression("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}" - "(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\\:[0-9]{1,5}){0,1}$"); } + static QString getIPAddress(const QString &host); + static QString getStringBetween(const QString &s, const QString &a, const QString &b); + static bool checkIPv4Format(const QString &ip); + static bool checkIpSubnetFormat(const QString &ip); + static QRegularExpression ipAddressRegExp() + { + return QRegularExpression("^((25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])(\\.(?!$)|$)){4}$"); + } + static QRegularExpression ipAddressPortRegExp() + { + return QRegularExpression("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}" + "(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\\:[0-9]{1,5}){0,1}$"); + } - static QRegExp ipAddressWithSubnetRegExp() { return QRegExp("(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}" - "(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\\/[0-9]{1,2}){0,1}"); } + static QRegExp ipAddressWithSubnetRegExp() + { + return QRegExp("(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}" + "(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\\/[0-9]{1,2}){0,1}"); + } - static QRegExp ipNetwork24RegExp() { return QRegExp("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}" - "0$"); } + static QRegExp ipNetwork24RegExp() + { + return QRegExp("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}" + "0$"); + } - static QRegExp ipPortRegExp() { return QRegExp("^()([1-9]|[1-5]?[0-9]{2,4}|6[1-4][0-9]{3}|65[1-4][0-9]{2}|655[1-2][0-9]|6553[1-5])$"); } + static QRegExp ipPortRegExp() + { + return QRegExp("^()([1-9]|[1-5]?[0-9]{2,4}|6[1-4][0-9]{3}|65[1-4][0-9]{2}|655[1-2][0-9]|6553[1-5])$"); + } - static QRegExp domainRegExp() { return QRegExp("(((?!\\-))(xn\\-\\-)?[a-z0-9\\-_]{0,61}[a-z0-9]{1,1}\\.)*(xn\\-\\-)?([a-z0-9\\-]{1,61}|[a-z0-9\\-]{1,30})\\.[a-z]{2,}"); } - static bool processIsRunning(const QString& fileName); + static QRegExp domainRegExp() + { + return QRegExp("(((?!\\-))(xn\\-\\-)?[a-z0-9\\-_]{0,61}[a-z0-9]{1,1}\\.)*(xn\\-\\-)?([a-z0-9\\-]{1,61}|[a-z0-" + "9\\-]{1,30})\\.[a-z]{2,}"); + } + static bool processIsRunning(const QString &fileName); static void killProcessByName(const QString &name); static QString netMaskFromIpWithSubnet(const QString ip); @@ -51,18 +70,6 @@ public: static QString wireguardExecPath(); static QString certUtilPath(); - static void saveFile(const QString &fileExtension, - const QString &caption, - const QString &fileName, - const QString &data); - - static QString getFileName(QWidget *parent = nullptr, - const QString &caption = QString(), - const QString &dir = QString(), - const QString &filter = QString(), - QString *selectedFilter = nullptr, - QFileDialog::Options options = QFileDialog::Options()); - #ifdef Q_OS_WIN static bool signalCtrl(DWORD dwProcessId, DWORD dwCtrlEvent); #endif diff --git a/service/server/CMakeLists.txt b/service/server/CMakeLists.txt index 9046f687..5d421763 100644 --- a/service/server/CMakeLists.txt +++ b/service/server/CMakeLists.txt @@ -6,7 +6,7 @@ project(${PROJECT}) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) -find_package(Qt6 REQUIRED COMPONENTS Core Network RemoteObjects Core5Compat Widgets) +find_package(Qt6 REQUIRED COMPONENTS Core Network RemoteObjects Core5Compat) qt_standard_project_setup() configure_file(${CMAKE_SOURCE_DIR}/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h) @@ -188,7 +188,7 @@ include_directories( ) add_executable(${PROJECT} ${SOURCES} ${HEADERS}) -target_link_libraries(${PROJECT} PRIVATE Qt6::Core Qt6::Network Qt6::RemoteObjects Qt6::Core5Compat Qt6::Widgets ${LIBS}) +target_link_libraries(${PROJECT} PRIVATE Qt6::Core Qt6::Network Qt6::RemoteObjects Qt6::Core5Compat ${LIBS}) target_compile_definitions(${PROJECT} PRIVATE "MZ_$") if(CMAKE_BUILD_TYPE STREQUAL "Debug") From 4c79905f5bd56f21b0e9778f5599e9a8504cd56f Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 24 Aug 2023 14:53:52 +0500 Subject: [PATCH 070/278] added autostart and start minimized options - added disabling split tunneling when selecting the wireguard protocol - if for macos the application is minimized to tray, then now it is not displayed in the dock --- client/amnezia_application.cpp | 26 +++++-- client/ui/controllers/pageController.cpp | 24 +++++- client/ui/controllers/pageController.h | 7 +- client/ui/controllers/settingsController.cpp | 21 ++++++ client/ui/controllers/settingsController.h | 6 ++ client/ui/macos_util.mm | 74 +++++++++++++------ .../ui/qml/Pages2/PageSettingsApplication.qml | 43 ++++++++++- .../ui/qml/Pages2/PageSettingsConnection.qml | 16 ++-- 8 files changed, 173 insertions(+), 44 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index ba4499a1..9ecc5df4 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -129,14 +129,16 @@ void AmneziaApplication::init() } } - // #ifdef Q_OS_WIN - // if (m_parser.isSet("a")) m_uiLogic->showOnStartup(); - // else emit m_uiLogic->show(); - // #else - // m_uiLogic->showOnStartup(); - // #endif +#ifdef Q_OS_WIN + if (m_parser.isSet("a")) + m_pageController->showOnStartup(); + else + emit m_pageController->raiseMainWindow(); +#else + m_pageController->showOnStartup(); +#endif - // TODO - fix + // TODO - fix #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) if (isPrimary()) { QObject::connect(this, &SingleApplication::instanceStarted, m_pageController.get(), [this]() { @@ -276,6 +278,14 @@ void AmneziaApplication::initModels() m_sitesModel.reset(new SitesModel(m_settings, this)); m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get()); + connect(m_containersModel.get(), &ContainersModel::defaultContainerChanged, this, [this]() { + if (m_containersModel->getDefaultContainer() == DockerContainer::WireGuard + && m_sitesModel->getRouteMode() != Settings::RouteMode::VpnAllSites) { + m_sitesModel->setRouteMode(Settings::RouteMode::VpnAllSites); + emit m_pageController->showNotificationMessage( + tr("Split tunneling for WireGuard is not implemented, the option was disabled")); + } + }); m_protocolsModel.reset(new ProtocolsModel(m_settings, this)); m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get()); @@ -306,7 +316,7 @@ void AmneziaApplication::initControllers() m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel, m_vpnConnection)); m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get()); - m_pageController.reset(new PageController(m_serversModel)); + m_pageController.reset(new PageController(m_serversModel, m_settings)); m_engine->rootContext()->setContextProperty("PageController", m_pageController.get()); m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_settings)); diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/pageController.cpp index 36d5ba7c..d0c219c1 100644 --- a/client/ui/controllers/pageController.cpp +++ b/client/ui/controllers/pageController.cpp @@ -5,9 +5,13 @@ #include "../../platforms/android/androidutils.h" #include #endif +#if defined Q_OS_MAC + #include "ui/macos_util.h" +#endif -PageController::PageController(const QSharedPointer &serversModel, QObject *parent) - : QObject(parent), m_serversModel(serversModel) +PageController::PageController(const QSharedPointer &serversModel, + const std::shared_ptr &settings, QObject *parent) + : QObject(parent), m_serversModel(serversModel), m_settings(settings) { #ifdef Q_OS_ANDROID // Change color of navigation and status bar's @@ -23,6 +27,9 @@ PageController::PageController(const QSharedPointer &serversModel, } }); #endif + + connect(this, &PageController::raiseMainWindow, []() { setDockIconVisible(true); }); + connect(this, &PageController::hideMainWindow, []() { setDockIconVisible(false); }); } QString PageController::getInitialPage() @@ -88,3 +95,16 @@ void PageController::updateNavigationBarColor(const int color) }); #endif } + +void PageController::showOnStartup() +{ + if (!m_settings->isStartMinimized()) { + emit raiseMainWindow(); + } else { +#ifdef Q_OS_WIN + emit hideMainWindow(); +#elif defined Q_OS_MACX + setDockIconVisible(false); +#endif + } +} diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 1948ed11..1dd16090 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -63,7 +63,8 @@ class PageController : public QObject { Q_OBJECT public: - explicit PageController(const QSharedPointer &serversModel, QObject *parent = nullptr); + explicit PageController(const QSharedPointer &serversModel, const std::shared_ptr &settings, + QObject *parent = nullptr); public slots: QString getInitialPage(); @@ -75,6 +76,8 @@ public slots: unsigned int getInitialPageNavigationBarColor(); void updateNavigationBarColor(const int color); + void showOnStartup(); + signals: void goToPageHome(); void goToPageSettings(); @@ -100,6 +103,8 @@ signals: private: QSharedPointer m_serversModel; + + std::shared_ptr m_settings; }; #endif // PAGECONTROLLER_H diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index b56e48ad..f09ebc21 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -4,6 +4,7 @@ #include "fileUtilites.h" #include "logger.h" +#include "ui/qautostart.h" #include "version.h" SettingsController::SettingsController(const QSharedPointer &serversModel, @@ -137,3 +138,23 @@ void SettingsController::toggleAutoConnect(bool enable) { m_settings->setAutoConnect(enable); } + +bool SettingsController::isAutoStartEnabled() +{ + return Autostart::isAutostart(); +} + +void SettingsController::toggleAutoStart(bool enable) +{ + Autostart::setAutostart(enable); +} + +bool SettingsController::isStartMinimizedEnabled() +{ + return m_settings->isStartMinimized(); +} + +void SettingsController::toggleStartMinimized(bool enable) +{ + m_settings->setStartMinimized(enable); +} diff --git a/client/ui/controllers/settingsController.h b/client/ui/controllers/settingsController.h index a0a29ebf..3ad602b7 100644 --- a/client/ui/controllers/settingsController.h +++ b/client/ui/controllers/settingsController.h @@ -48,6 +48,12 @@ public slots: bool isAutoConnectEnabled(); void toggleAutoConnect(bool enable); + bool isAutoStartEnabled(); + void toggleAutoStart(bool enable); + + bool isStartMinimizedEnabled(); + void toggleStartMinimized(bool enable); + signals: void primaryDnsChanged(); void secondaryDnsChanged(); diff --git a/client/ui/macos_util.mm b/client/ui/macos_util.mm index 8adda58a..3947b89b 100644 --- a/client/ui/macos_util.mm +++ b/client/ui/macos_util.mm @@ -1,60 +1,86 @@ -#include -#include #include "macos_util.h" +#include +#include + +#include -#import #import +#import + +// void setDockIconVisible(bool visible) +//{ +// QProcess process; +// process.start( +// "osascript", +// { "-e tell application \"System Events\" to get properties of (get application process \"AmneziaVPN\")" }); +// process.waitForFinished(3000); +// const auto output = QString::fromLocal8Bit(process.readAllStandardOutput()); + +// qDebug() << output; + +// if (visible) { +// [NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory]; +// } else { +// [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; +// } +//} void setDockIconVisible(bool visible) { - if (!visible) { - [NSApp setActivationPolicy: NSApplicationActivationPolicyAccessory]; + ProcessSerialNumber psn = { 0, kCurrentProcess }; + if (visible) { + TransformProcessType(&psn, kProcessTransformToForegroundApplication); } else { - [NSApp setActivationPolicy: NSApplicationActivationPolicyRegular]; + TransformProcessType(&psn, kProcessTransformToBackgroundApplication); } } -//this Objective-c class is used to override the action of system close button and zoom button -//https://stackoverflow.com/questions/27643659/setting-c-function-as-selector-for-nsbutton-produces-no-results -@interface ButtonPasser : NSObject{ +// this Objective-c class is used to override the action of system close button and zoom button +// https://stackoverflow.com/questions/27643659/setting-c-function-as-selector-for-nsbutton-produces-no-results +@interface ButtonPasser : NSObject { } -@property(readwrite) QMainWindow* window; +@property (readwrite) QMainWindow *window; + (void)closeButtonAction:(id)sender; - (void)zoomButtonAction:(id)sender; @end -@implementation ButtonPasser{ +@implementation ButtonPasser { } + (void)closeButtonAction:(id)sender { Q_UNUSED(sender); ProcessSerialNumber pn; - GetFrontProcess (&pn); - ShowHideProcess(&pn,false); + GetFrontProcess(&pn); + ShowHideProcess(&pn, false); } - (void)zoomButtonAction:(id)sender { Q_UNUSED(sender); - if (0 == self.window) return; - if (self.window->isMaximized()) self.window->showNormal(); - else self.window->showMaximized(); + if (0 == self.window) + return; + if (self.window->isMaximized()) + self.window->showNormal(); + else + self.window->showMaximized(); } @end void fixWidget(QWidget *widget) { NSView *view = (NSView *)widget->winId(); - if (0 == view) return; + if (0 == view) + return; NSWindow *window = view.window; - if (0 == window) return; + if (0 == window) + return; - //override the action of close button - //https://stackoverflow.com/questions/27643659/setting-c-function-as-selector-for-nsbutton-produces-no-results - //https://developer.apple.com/library/content/documentation/General/Conceptual/CocoaEncyclopedia/Target-Action/Target-Action.html -// NSButton *closeButton = [window standardWindowButton:NSWindowCloseButton]; -// [closeButton setTarget:[ButtonPasser class]]; -// [closeButton setAction:@selector(closeButtonAction:)]; + // override the action of close button + // https://stackoverflow.com/questions/27643659/setting-c-function-as-selector-for-nsbutton-produces-no-results + // https://developer.apple.com/library/content/documentation/General/Conceptual/CocoaEncyclopedia/Target-Action/Target-Action.html + // NSButton *closeButton = [window standardWindowButton:NSWindowCloseButton]; + // [closeButton setTarget:[ButtonPasser class]]; + // [closeButton setAction:@selector(closeButtonAction:)]; [[window standardWindowButton:NSWindowZoomButton] setHidden:YES]; [[window standardWindowButton:NSWindowMiniaturizeButton] setHidden:YES]; diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index 9f2fe91a..7b628037 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -43,9 +43,50 @@ PageType { headerText: qsTr("Application") } + SwitcherType { + visible: !GC.isMobile() + + Layout.fillWidth: true + Layout.margins: 16 + + text: qsTr("Auto start") + descriptionText: qsTr("Launch the application every time ") + Qt.platform.os + qsTr(" starts") + + checked: SettingsController.isAutoStartEnabled() + onCheckedChanged: { + if (checked !== SettingsController.isAutoStartEnabled()) { + SettingsController.toggleAutoStart(checked) + } + } + } + + DividerType { + visible: !GC.isMobile() + } + + SwitcherType { + visible: !GC.isMobile() + + Layout.fillWidth: true + Layout.margins: 16 + + text: qsTr("Start minimized") + descriptionText: qsTr("Launch application minimized") + + checked: SettingsController.isStartMinimizedEnabled() + onCheckedChanged: { + if (checked !== SettingsController.isStartMinimizedEnabled()) { + SettingsController.toggleStartMinimized(checked) + } + } + } + + DividerType { + visible: !GC.isMobile() + } + LabelWithButtonType { Layout.fillWidth: true - Layout.topMargin: 16 text: qsTr("Language") descriptionText: LanguageModel.currentLanguageName diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml index 0765f7d2..0692abda 100644 --- a/client/ui/qml/Pages2/PageSettingsConnection.qml +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -42,11 +42,10 @@ PageType { } SwitcherType { + visible: !GC.isMobile() + Layout.fillWidth: true - Layout.topMargin: 16 - Layout.bottomMargin: 16 - Layout.leftMargin: 16 - Layout.rightMargin: 16 + Layout.margins: 16 text: qsTr("Auto connect") descriptionText: qsTr("Connect to VPN on app start") @@ -59,12 +58,13 @@ PageType { } } + DividerType { + visible: !GC.isMobile() + } + SwitcherType { Layout.fillWidth: true - Layout.topMargin: 16 - Layout.bottomMargin: 16 - Layout.leftMargin: 16 - Layout.rightMargin: 16 + Layout.margins: 16 text: qsTr("Use AmneziaDNS if installed on the server") From c271235d169c9edb4dfe9d556ecd5f28ef2a71ba Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 24 Aug 2023 16:22:55 +0500 Subject: [PATCH 071/278] added confirmation dialog when clearing logs and notification after clearing --- client/ui/qml/Pages2/PageSettingsLogging.qml | 24 ++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml index 5138f9a3..e14be439 100644 --- a/client/ui/qml/Pages2/PageSettingsLogging.qml +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -4,9 +4,9 @@ import QtQuick.Layouts import PageEnum 1.0 -import "./" import "../Controls2" import "../Config" +import "../Components" import "../Controls2/TextTypes" PageType { @@ -121,7 +121,23 @@ PageType { image: "qrc:/images/controls/delete.svg" - onClicked: SettingsController.clearLogs() + onClicked: function() { + questionDrawer.headerText = qsTr("Clear logs?") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + PageController.showBusyIndicator(true) + SettingsController.clearLogs() + PageController.showBusyIndicator(false) + PageController.showNotificationMessage(qsTr("Logs have been cleaned up")) + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true + } } CaptionTextType { @@ -133,6 +149,10 @@ PageType { } } } + + QuestionDrawer { + id: questionDrawer + } } } } From 259eff3fea891b674df4feb031fd5cddc8e814f1 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 24 Aug 2023 16:25:51 +0500 Subject: [PATCH 072/278] upgraded app version --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index eac7f0a9..e7231c56 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.0.1.1 +project(${PROJECT} VERSION 4.0.2.1 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) From 3f7e7f260113b65db04b7d595be921212041bec2 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 25 Aug 2023 09:20:42 +0500 Subject: [PATCH 073/278] fixed native wireguard config import if there is no port in the Endpoint field --- client/ui/controllers/importController.cpp | 9 +++++++-- client/ui/controllers/pageController.cpp | 2 ++ client/ui/controllers/sitesController.cpp | 1 + 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index b5d10abf..81ab313b 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -225,12 +225,17 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data) QJsonObject lastConfig; lastConfig[config_key::config] = data; - const static QRegularExpression hostNameAndPortRegExp("Endpoint = (.*):([0-9]*)"); + const static QRegularExpression hostNameAndPortRegExp("Endpoint = (.*)(?::([0-9]*))?"); QRegularExpressionMatch hostNameAndPortMatch = hostNameAndPortRegExp.match(data); QString hostName; QString port; - if (hostNameAndPortMatch.hasMatch()) { + if (hostNameAndPortMatch.hasCaptured(1)) { hostName = hostNameAndPortMatch.captured(1); + } /*else { + qDebug() << "send error?" + }*/ + + if (hostNameAndPortMatch.hasCaptured(2)) { port = hostNameAndPortMatch.captured(2); } diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/pageController.cpp index d0c219c1..84d2ebf2 100644 --- a/client/ui/controllers/pageController.cpp +++ b/client/ui/controllers/pageController.cpp @@ -28,8 +28,10 @@ PageController::PageController(const QSharedPointer &serversModel, }); #endif +#if defined Q_OS_MACX connect(this, &PageController::raiseMainWindow, []() { setDockIconVisible(true); }); connect(this, &PageController::hideMainWindow, []() { setDockIconVisible(false); }); +#endif } QString PageController::getInitialPage() diff --git a/client/ui/controllers/sitesController.cpp b/client/ui/controllers/sitesController.cpp index d19f5758..80cac698 100644 --- a/client/ui/controllers/sitesController.cpp +++ b/client/ui/controllers/sitesController.cpp @@ -4,6 +4,7 @@ #include #include "fileUtilites.h" +#include "utilities.h" SitesController::SitesController(const std::shared_ptr &settings, const QSharedPointer &vpnConnection, From 29bef052c7bf3fead0afa76a1e2c0af890c329d7 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sat, 26 Aug 2023 10:08:50 +0300 Subject: [PATCH 074/278] minor ui fixes --- CMakeLists.txt | 2 +- client/ui/qml/Components/ShareConnectionDrawer.qml | 2 +- client/ui/qml/Controls2/CheckBoxType.qml | 2 +- client/ui/qml/Controls2/DropDownType.qml | 12 ++++++++---- client/ui/qml/Controls2/HorizontalRadioButton.qml | 11 ++++++----- client/ui/qml/Controls2/ProgressBarType.qml | 2 +- client/ui/qml/Controls2/SwitcherType.qml | 9 +++++---- client/ui/qml/Controls2/TabButtonType.qml | 8 +++++++- client/ui/qml/Controls2/TabImageButtonType.qml | 9 ++++++++- client/ui/qml/Controls2/TextAreaType.qml | 2 +- client/ui/qml/Controls2/TextFieldWithHeaderType.qml | 2 +- client/ui/qml/Pages2/PageHome.qml | 8 +++++++- client/ui/qml/Pages2/PageProtocolRaw.qml | 2 +- client/ui/qml/Pages2/PageSettings.qml | 4 +++- client/ui/qml/Pages2/PageShare.qml | 9 ++++++--- client/ui/qml/Pages2/PageStart.qml | 8 -------- 16 files changed, 57 insertions(+), 35 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e7231c56..4560bdd0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ project(${PROJECT} VERSION 4.0.2.1 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) -set(RELEASE_DATE "2023-08-16") +set(RELEASE_DATE "2023-08-25") set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 368d503a..05684ba8 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -163,7 +163,7 @@ DrawerType { height: 24 color: "#D7D8DB" - selectionColor: "#412102" + selectionColor: "#633303" selectedTextColor: "#D7D8DB" font.pixelSize: 16 diff --git a/client/ui/qml/Controls2/CheckBoxType.qml b/client/ui/qml/Controls2/CheckBoxType.qml index 2724a30c..1ad3b412 100644 --- a/client/ui/qml/Controls2/CheckBoxType.qml +++ b/client/ui/qml/Controls2/CheckBoxType.qml @@ -21,7 +21,7 @@ CheckBox { property string defaultBorderColor: "#D7D8DB" property string checkedBorderColor: "#FBB26A" - property string checkedBorderDisabledColor: "#5A330C" + property string checkedBorderDisabledColor: "#402102" property string checkedImageColor: "#FBB26A" property string pressedImageColor: "#A85809" diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 17151630..c21fa48f 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -27,7 +27,9 @@ Item { property string rootButtonDefaultBorderColor: "#2C2D30" property string rootButtonPressedBorderColor: "#D7D8DB" - property int rootButtonTextMargins: 16 + property int rootButtonTextLeftMargins: 16 + property int rootButtonTextTopMargin: 16 + property int rootButtonTextBottomMargin: 16 property real drawerHeight: 0.9 property Component listView @@ -76,9 +78,9 @@ Item { spacing: 0 ColumnLayout { - Layout.leftMargin: rootButtonTextMargins - Layout.topMargin: rootButtonTextMargins - Layout.bottomMargin: rootButtonTextMargins + Layout.leftMargin: rootButtonTextLeftMargins + Layout.topMargin: rootButtonTextTopMargin + Layout.bottomMargin: rootButtonTextBottomMargin LabelTextType { Layout.fillWidth: true @@ -104,6 +106,8 @@ Item { } ImageButtonType { + Layout.rightMargin: 16 + hoverEnabled: false image: rootButtonImage imageColor: rootButtonImageColor diff --git a/client/ui/qml/Controls2/HorizontalRadioButton.qml b/client/ui/qml/Controls2/HorizontalRadioButton.qml index 1ac5840a..81cc8ec0 100644 --- a/client/ui/qml/Controls2/HorizontalRadioButton.qml +++ b/client/ui/qml/Controls2/HorizontalRadioButton.qml @@ -75,19 +75,20 @@ RadioButton { ColumnLayout { id: content anchors.fill: parent - spacing: 16 + spacing: 0 ButtonTextType { text: root.text color: root.enabled ? root.textColor : root.textDisabledColor Layout.fillWidth: true - Layout.rightMargin: 16 - Layout.leftMargin: 16 - Layout.topMargin: 16 - Layout.bottomMargin: 16 + Layout.rightMargin: 24 + Layout.leftMargin: 24 + Layout.topMargin: 12 + Layout.bottomMargin: 12 horizontalAlignment: Qt.AlignHCenter + verticalAlignment: Qt.AlignVCenter } } diff --git a/client/ui/qml/Controls2/ProgressBarType.qml b/client/ui/qml/Controls2/ProgressBarType.qml index 183c3736..e642c3eb 100644 --- a/client/ui/qml/Controls2/ProgressBarType.qml +++ b/client/ui/qml/Controls2/ProgressBarType.qml @@ -8,7 +8,7 @@ ProgressBar { implicitHeight: 4 background: Rectangle { - color: "#412102" + color: "#633303" } contentItem: Item { diff --git a/client/ui/qml/Controls2/SwitcherType.qml b/client/ui/qml/Controls2/SwitcherType.qml index aca5ba0b..ee7372f5 100644 --- a/client/ui/qml/Controls2/SwitcherType.qml +++ b/client/ui/qml/Controls2/SwitcherType.qml @@ -14,13 +14,13 @@ Switch { property string textColor: "#D7D8DB" property string textDisabledColor: "#878B91" - property string checkedIndicatorColor: "#412102" + property string checkedIndicatorColor: "#633303" property string defaultIndicatorColor: "transparent" - property string checkedDisabledIndicatorColor: "#5A330C" + property string checkedDisabledIndicatorColor: "#402102" - property string checkedIndicatorBorderColor: "#412102" + property string checkedIndicatorBorderColor: "#633303" property string defaultIndicatorBorderColor: "#494B50" - property string checkedDisabledIndicatorBorderColor: "#5A330C" + property string checkedDisabledIndicatorBorderColor: "#402102" property string checkedInnerCircleColor: "#FBB26A" property string defaultInnerCircleColor: "#D7D8DB" @@ -40,6 +40,7 @@ Switch { anchors.left: content.right anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: 4 implicitWidth: 52 implicitHeight: 32 diff --git a/client/ui/qml/Controls2/TabButtonType.qml b/client/ui/qml/Controls2/TabButtonType.qml index 4699dfcd..f8011f0d 100644 --- a/client/ui/qml/Controls2/TabButtonType.qml +++ b/client/ui/qml/Controls2/TabButtonType.qml @@ -4,7 +4,7 @@ import QtQuick.Controls TabButton { id: root - property string hoveredColor: "#412102" + property string hoveredColor: "#633303" property string defaultColor: "#2C2D30" property string selectedColor: "#FBB26A" @@ -39,6 +39,12 @@ TabButton { } } + MouseArea { + anchors.fill: background + cursorShape: Qt.PointingHandCursor + enabled: false + } + contentItem: Text { anchors.fill: background height: 24 diff --git a/client/ui/qml/Controls2/TabImageButtonType.qml b/client/ui/qml/Controls2/TabImageButtonType.qml index 06b77e6a..4d745a0b 100644 --- a/client/ui/qml/Controls2/TabImageButtonType.qml +++ b/client/ui/qml/Controls2/TabImageButtonType.qml @@ -4,7 +4,7 @@ import QtQuick.Controls TabButton { id: root - property string hoveredColor: "#412102" + property string hoveredColor: "#633303" property string defaultColor: "#D7D8DB" property string selectedColor: "#FBB26A" @@ -18,7 +18,14 @@ TabButton { icon.color: isSelected ? selectedColor : defaultColor background: Rectangle { + id: background anchors.fill: parent color: "transparent" } + + MouseArea { + anchors.fill: background + cursorShape: Qt.PointingHandCursor + enabled: false + } } diff --git a/client/ui/qml/Controls2/TextAreaType.qml b/client/ui/qml/Controls2/TextAreaType.qml index 9958f2e9..a75ea55d 100644 --- a/client/ui/qml/Controls2/TextAreaType.qml +++ b/client/ui/qml/Controls2/TextAreaType.qml @@ -29,7 +29,7 @@ Rectangle { anchors.bottomMargin: 16 color: "#D7D8DB" - selectionColor: "#412102" + selectionColor: "#633303" selectedTextColor: "#D7D8DB" placeholderTextColor: "#878B91" diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml index 3f31c224..048a564d 100644 --- a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -74,7 +74,7 @@ Item { placeholderText: root.textFieldPlaceholderText placeholderTextColor: "#494B50" - selectionColor: "#412102" + selectionColor: "#633303" selectedTextColor: "#D7D8DB" font.pixelSize: 16 diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index b756511b..c5add5d8 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -70,6 +70,8 @@ PageType { Layout.topMargin: 24 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + spacing: 0 + Header1TextType { text: root.defaultServerName } @@ -78,6 +80,8 @@ PageType { Layout.preferredWidth: 18 Layout.preferredHeight: 18 + Layout.leftMargin: 12 + source: "qrc:/images/controls/chevron-down.svg" } } @@ -152,7 +156,8 @@ PageType { rootButtonBackgroundColor: "#D7D8DB" rootButtonHoveredBorderColor: "transparent" rootButtonPressedBorderColor: "transparent" - rootButtonTextMargins: 8 + rootButtonTextTopMargin: 8 + rootButtonTextBottomMargin: 8 text: root.defaultContainerName textColor: "#0E0E11" @@ -303,6 +308,7 @@ PageType { ImageButtonType { image: "qrc:/images/controls/settings.svg" + imageColor: "#D7D8DB" implicitWidth: 56 implicitHeight: 56 diff --git a/client/ui/qml/Pages2/PageProtocolRaw.qml b/client/ui/qml/Pages2/PageProtocolRaw.qml index e82b0ff5..5c70b1c0 100644 --- a/client/ui/qml/Pages2/PageProtocolRaw.qml +++ b/client/ui/qml/Pages2/PageProtocolRaw.qml @@ -142,7 +142,7 @@ PageType { height: 24 color: "#D7D8DB" - selectionColor: "#412102" + selectionColor: "#633303" selectedTextColor: "#D7D8DB" font.pixelSize: 16 diff --git a/client/ui/qml/Pages2/PageSettings.qml b/client/ui/qml/Pages2/PageSettings.qml index f430a004..6c088a70 100644 --- a/client/ui/qml/Pages2/PageSettings.qml +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -26,9 +26,11 @@ PageType { anchors.left: parent.left anchors.right: parent.right + spacing: 0 + HeaderType { Layout.fillWidth: true - Layout.topMargin: 20 + Layout.topMargin: 24 Layout.rightMargin: 16 Layout.leftMargin: 16 diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 120e04fc..135c7fbc 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -106,9 +106,11 @@ PageType { anchors.rightMargin: 16 anchors.leftMargin: 16 + spacing: 0 + HeaderType { Layout.fillWidth: true - Layout.topMargin: 20 + Layout.topMargin: 24 headerText: qsTr("VPN Access") } @@ -161,6 +163,7 @@ PageType { ParagraphTextType { Layout.fillWidth: true Layout.topMargin: 24 + Layout.bottomMargin: 24 text: accessTypeSelector.currentIndex === 0 ? qsTr("VPN access without the ability to manage the server") : qsTr("Full access to server") @@ -171,7 +174,7 @@ PageType { id: serverSelector Layout.fillWidth: true - Layout.topMargin: 24 + Layout.topMargin: 16 drawerHeight: 0.4375 @@ -385,7 +388,7 @@ PageType { BasicButtonType { Layout.fillWidth: true - Layout.topMargin: 32 + Layout.topMargin: 40 enabled: shareButtonEnabled diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 5c2a13df..e4cc02ee 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -163,14 +163,6 @@ PageType { } } - MouseArea { - anchors.fill: tabBar - anchors.leftMargin: shareTabButton.visible ? 96 : 128 - anchors.rightMargin: shareTabButton.visible ? 96 : 128 - cursorShape: Qt.PointingHandCursor - enabled: false - } - BusyIndicatorType { id: busyIndicator anchors.centerIn: parent From fe08fd3f0af9e7a7385229db8c4687055260a3d8 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 28 Aug 2023 11:06:58 +0300 Subject: [PATCH 075/278] moved the connect button to the center of the screen --- CMakeLists.txt | 4 ++-- client/ui/controllers/connectionController.cpp | 2 +- client/ui/qml/Pages2/PageHome.qml | 5 ++++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4560bdd0..aec2a974 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,11 +2,11 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.0.2.1 +project(${PROJECT} VERSION 4.0.3.1 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) -set(RELEASE_DATE "2023-08-25") +set(RELEASE_DATE "2023-08-26") set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index 45f24d7f..d3813086 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -72,7 +72,7 @@ void ConnectionController::onConnectionStateChanged(Vpn::ConnectionState state) case Vpn::ConnectionState::Connected: { m_isConnectionInProgress = false; m_isConnected = true; - m_connectionStateText = tr("Disconnect"); + m_connectionStateText = tr("Connected"); break; } case Vpn::ConnectionState::Connecting: { diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index c5add5d8..d40f8748 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -27,7 +27,10 @@ PageType { property string defaultContainerName: ContainersModel.defaultContainerName ConnectButton { - anchors.centerIn: parent + anchors.top: parent.top + anchors.bottom: buttonBackground.top + anchors.right: parent.right + anchors.left: parent.left } Connections { From 639c18395b32081c422eab99cf5b01c7f7bbfedd Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 28 Aug 2023 14:18:41 +0300 Subject: [PATCH 076/278] fixed display of notification about successful clearing of cached profiles - limited the input for the Port field to only numeric values, in the range 1-65535 --- .../qml/Components/ShareConnectionDrawer.qml | 2 +- .../qml/Pages2/PageProtocolCloakSettings.qml | 1 + .../Pages2/PageProtocolOpenVpnSettings.qml | 1 + .../PageProtocolShadowSocksSettings.qml | 1 + .../ui/qml/Pages2/PageSettingsServerData.qml | 8 +++--- .../Pages2/PageSetupWizardConfigSource.qml | 2 +- .../qml/Pages2/PageSetupWizardCredentials.qml | 25 ------------------- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 13 ++++++++-- .../PageSetupWizardProtocolSettings.qml | 1 + 9 files changed, 22 insertions(+), 32 deletions(-) diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 05684ba8..c369af42 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -227,7 +227,7 @@ DrawerType { visible: ExportController.qrCodesCount > 0 horizontalAlignment: Text.AlignHCenter - text: qsTr("To read the QR code in the Amnezia app, select \"Add Server\" → \"I have connection details\"") + text: qsTr("To read the QR code in the Amnezia app, select \"Add server\" → \"I have data to connect\" → \"QR code, key or settings file\"") } } } diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml index 7273b86d..b53fdfdf 100644 --- a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -103,6 +103,7 @@ PageType { headerText: qsTr("Port") textFieldText: port textField.maximumLength: 5 + textField.validator: IntValidator { bottom: 1; top: 65535 } textField.onEditingFinished: { if (textFieldText !== port) { diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index fb2258c5..659193ca 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -134,6 +134,7 @@ PageType { headerText: qsTr("Port") textFieldText: port textField.maximumLength: 5 + textField.validator: IntValidator { bottom: 1; top: 65535 } textField.onEditingFinished: { if (textFieldText !== port) { diff --git a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml index 7b78bc07..fe0ef8c3 100644 --- a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml @@ -89,6 +89,7 @@ PageType { headerText: qsTr("Port") textFieldText: port textField.maximumLength: 5 + textField.validator: IntValidator { bottom: 1; top: 65535 } textField.onEditingFinished: { if (textFieldText !== port) { diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 9ff99193..15c5e531 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -89,13 +89,15 @@ PageType { clickedFunction: function() { questionDrawer.headerText = qsTr("Clear cached profiles?") - questionDrawer.descriptionText = qsTr("some description") + questionDrawer.descriptionText = qsTr("") questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { questionDrawer.visible = false - ContainersModel.clearCachedProfiles() + PageController.showBusyIndicator(true) + SettingsController.clearCachedProfiles() + PageController.showBusyIndicator(false) } questionDrawer.noButtonFunction = function() { questionDrawer.visible = false @@ -165,7 +167,7 @@ PageType { clickedFunction: function() { questionDrawer.headerText = qsTr("Clear server from Amnezia software?") - questionDrawer.descriptionText = qsTr(" All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted.") + questionDrawer.descriptionText = qsTr("All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted.") questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.noButtonText = qsTr("Cancel") diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index f0155667..45aad9d0 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -49,7 +49,7 @@ PageType { headerText: qsTr("Server connection") descriptionText: qsTr("Do not use connection code from public sources. It may have been created to intercept your data.\n -It's okay if a friend passed the code.") +It's okay as long as it's from someone you trust.") } Header2TextType { diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index 5187a6e3..d089a70d 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -107,31 +107,6 @@ PageType { goToPage(PageEnum.PageSetupWizardEasy) } } - -// 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("Select protocol to install") - -// onClicked: function() { -// if (!isCredentialsFilled()) { -// return -// } - -// InstallController.setShouldCreateServer(true) -// InstallController.setCurrentlyInstalledServerCredentials(hostname.textField.text, username.textField.text, secretData.textField.text) - -// goToPage(PageEnum.PageSetupWizardProtocols) -// } -// } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index f2600f65..375a8332 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -45,7 +45,7 @@ PageType { id: fl anchors.top: backButton.bottom anchors.bottom: parent.bottom - contentHeight: content.implicitHeight + continueButton.anchors.bottomMargin + contentHeight: content.implicitHeight + setupLaterButton.anchors.bottomMargin Column { id: content @@ -126,7 +126,9 @@ PageType { } } - DividerType {} + DividerType { + implicitWidth: parent.width + } CardType { implicitWidth: parent.width @@ -141,6 +143,11 @@ PageType { } } + Item { + implicitWidth: 1 + implicitHeight: 1 + } + BasicButtonType { id: continueButton @@ -163,6 +170,8 @@ PageType { } BasicButtonType { + id: setupLaterButton + implicitWidth: parent.width anchors.topMargin: 8 anchors.bottomMargin: 24 diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 2f5ed569..d64fa097 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -212,6 +212,7 @@ PageType { headerText: qsTr("Port") textField.maximumLength: 5 + textField.validator: IntValidator { bottom: 1; top: 65535 } } Rectangle { From 8f6aa950cd4a7c34cf8f9e9d855505f63120e3e5 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 28 Aug 2023 22:03:28 +0300 Subject: [PATCH 077/278] fixed conflicts after merge --- client/amnezia_application.cpp | 7 +- client/ui/controllers/importController.cpp | 15 ++ client/ui/controllers/settingsController.cpp | 12 ++ client/ui/controllers/sitesController.cpp | 12 ++ client/ui/pages_logic/StartPageLogic.cpp | 23 +-- client/vpnconnection.cpp | 175 ++++++++++--------- 6 files changed, 140 insertions(+), 104 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 93f73cd6..c4586748 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -21,8 +21,8 @@ #include "protocols/qml_register_protocols.h" #if defined(Q_OS_IOS) -#include "platforms/ios/QtAppDelegate-C-Interface.h" -#include "platforms/ios/ios_controller.h" + #include "platforms/ios/QtAppDelegate-C-Interface.h" + #include "platforms/ios/ios_controller.h" #endif #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) @@ -108,6 +108,9 @@ void AmneziaApplication::init() &ImportController::extractConfigFromData); connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, m_pageController.get(), &PageController::goToPageViewConfig); +#endif + +#ifdef Q_OS_IOS IosController::Instance()->initialize(); #endif diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 81ab313b..7f8b82a9 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -10,6 +10,9 @@ #include "../../platforms/android/androidutils.h" #include #endif +#ifdef Q_OS_IOS + #include +#endif #include "fileUtilites.h" namespace @@ -88,6 +91,18 @@ void ImportController::extractConfigFromFile() QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.vpn *.ovpn *.conf"); QFile file(fileName); + +#ifdef Q_OS_IOS + CFURLRef url = CFURLCreateWithFileSystemPath( + kCFAllocatorDefault, + CFStringCreateWithCharacters(0, reinterpret_cast(fileName.unicode()), fileName.length()), + kCFURLPOSIXPathStyle, 0); + + if (!CFURLStartAccessingSecurityScopedResource(url)) { + qDebug() << "Could not access path " << QUrl::fromLocalFile(fileName).toString(); + } +#endif + if (file.open(QIODevice::ReadOnly)) { QString data = file.readAll(); diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index f09ebc21..531d3c50 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -95,6 +95,18 @@ void SettingsController::restoreAppConfig() } QFile file(fileName); + +#ifdef Q_OS_IOS + CFURLRef url = CFURLCreateWithFileSystemPath( + kCFAllocatorDefault, + CFStringCreateWithCharacters(0, reinterpret_cast(fileName.unicode()), fileName.length()), + kCFURLPOSIXPathStyle, 0); + + if (!CFURLStartAccessingSecurityScopedResource(url)) { + qDebug() << "Could not access path " << QUrl::fromLocalFile(fileName).toString(); + } +#endif + file.open(QIODevice::ReadOnly); QByteArray data = file.readAll(); diff --git a/client/ui/controllers/sitesController.cpp b/client/ui/controllers/sitesController.cpp index 80cac698..891ddb6f 100644 --- a/client/ui/controllers/sitesController.cpp +++ b/client/ui/controllers/sitesController.cpp @@ -90,6 +90,18 @@ void SitesController::importSites(bool replaceExisting) } QFile file(fileName); + +#ifdef Q_OS_IOS + CFURLRef url = CFURLCreateWithFileSystemPath( + kCFAllocatorDefault, + CFStringCreateWithCharacters(0, reinterpret_cast(fileName.unicode()), fileName.length()), + kCFURLPOSIXPathStyle, 0); + + if (!CFURLStartAccessingSecurityScopedResource(url)) { + qDebug() << "Could not access path " << QUrl::fromLocalFile(fileName).toString(); + } +#endif + if (!file.open(QIODevice::ReadOnly)) { emit errorOccurred(tr("Can't open file: ") + fileName); return; diff --git a/client/ui/pages_logic/StartPageLogic.cpp b/client/ui/pages_logic/StartPageLogic.cpp index 9b89781f..891d67fb 100644 --- a/client/ui/pages_logic/StartPageLogic.cpp +++ b/client/ui/pages_logic/StartPageLogic.cpp @@ -19,17 +19,10 @@ #endif #ifdef Q_OS_IOS -#include + #include #endif -namespace { -enum class ConfigTypes { - Amnezia, - OpenVpn, - WireGuard -}; - -ConfigTypes checkConfigFormat(const QString &config) +namespace { enum class ConfigTypes { Amnezia, @@ -200,18 +193,18 @@ void StartPageLogic::onPushButtonImportOpenFile() return; QFile file(fileName); - + #ifdef Q_OS_IOS CFURLRef url = CFURLCreateWithFileSystemPath( - kCFAllocatorDefault, CFStringCreateWithCharacters(0, reinterpret_cast(fileName.unicode()), - fileName.length()), - kCFURLPOSIXPathStyle, 0); - + kCFAllocatorDefault, + CFStringCreateWithCharacters(0, reinterpret_cast(fileName.unicode()), fileName.length()), + kCFURLPOSIXPathStyle, 0); + if (!CFURLStartAccessingSecurityScopedResource(url)) { qDebug() << "Could not access path " << QUrl::fromLocalFile(fileName).toString(); } #endif - + file.open(QIODevice::ReadOnly); QByteArray data = file.readAll(); diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index 5c9e4c3e..1e8fd60a 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -5,41 +5,40 @@ #include #include -#include #include +#include #include -#include #include +#include #include #ifdef AMNEZIA_DESKTOP -#include "ipc.h" -#include "core/ipcclient.h" -#include + #include "core/ipcclient.h" + #include "ipc.h" + #include #endif #ifdef Q_OS_ANDROID -#include "../../platforms/android/android_controller.h" + #include "../../platforms/android/android_controller.h" #endif #ifdef Q_OS_IOS -#include "platforms/ios/ios_controller.h" + #include "platforms/ios/ios_controller.h" #endif #include "utilities.h" #include "vpnconnection.h" -VpnConnection::VpnConnection(std::shared_ptr settings, - std::shared_ptr configurator, QObject* parent) : QObject(parent), - m_settings(settings), - m_configurator(configurator), - m_checkTimer(new QTimer(this)) +VpnConnection::VpnConnection(std::shared_ptr settings, std::shared_ptr configurator, + QObject *parent) + : QObject(parent), m_settings(settings), m_configurator(configurator), m_checkTimer(new QTimer(this)) { m_checkTimer.setInterval(1000); #ifdef Q_OS_IOS - connect(IosController::Instance(), &IosController::connectionStateChanged, this, &VpnConnection::onConnectionStateChanged); + connect(IosController::Instance(), &IosController::connectionStateChanged, this, + &VpnConnection::onConnectionStateChanged); connect(IosController::Instance(), &IosController::bytesChanged, this, &VpnConnection::onBytesChanged); - + #endif } @@ -60,27 +59,23 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state) #ifdef AMNEZIA_DESKTOP if (IpcClient::Interface()) { - if (state == Vpn::ConnectionState::Connected){ + if (state == Vpn::ConnectionState::Connected) { IpcClient::Interface()->resetIpStack(); IpcClient::Interface()->flushDns(); if (m_settings->routeMode() != Settings::VpnAllSites) { IpcClient::Interface()->routeDeleteList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0"); - //qDebug() << "VpnConnection::onConnectionStateChanged :: adding custom routes, count:" << forwardIps.size(); + // qDebug() << "VpnConnection::onConnectionStateChanged :: adding custom routes, count:" << forwardIps.size(); } QString dns1 = m_vpnConfiguration.value(config_key::dns1).toString(); QString dns2 = m_vpnConfiguration.value(config_key::dns1).toString(); - IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), - QStringList() << dns1 << dns2); - + IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << dns1 << dns2); if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) { - QTimer::singleShot(1000, m_vpnProtocol.data(), [this](){ - addSitesRoutes(m_vpnProtocol->vpnGateway(), m_settings->routeMode()); - }); - } - else if (m_settings->routeMode() == Settings::VpnAllExceptSites) { + QTimer::singleShot(1000, m_vpnProtocol.data(), + [this]() { addSitesRoutes(m_vpnProtocol->vpnGateway(), m_settings->routeMode()); }); + } else if (m_settings->routeMode() == Settings::VpnAllExceptSites) { IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0/1"); IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "128.0.0.0/1"); @@ -88,9 +83,7 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state) addSitesRoutes(m_vpnProtocol->routeGateway(), m_settings->routeMode()); } - - } - else if (state == Vpn::ConnectionState::Error) { + } else if (state == Vpn::ConnectionState::Error) { IpcClient::Interface()->flushDns(); if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) { @@ -103,8 +96,7 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state) #ifdef Q_OS_IOS if (state == Vpn::ConnectionState::Connected) { m_checkTimer.start(); - } - else { + } else { m_checkTimer.stop(); } #endif @@ -125,8 +117,7 @@ void VpnConnection::addSitesRoutes(const QString &gw, Settings::RouteMode mode) for (auto i = m.constBegin(); i != m.constEnd(); ++i) { if (Utils::checkIpSubnetFormat(i.key())) { ips.append(i.key()); - } - else { + } else { if (Utils::checkIpSubnetFormat(i.value().toString())) { ips.append(i.value().toString()); } @@ -139,24 +130,24 @@ void VpnConnection::addSitesRoutes(const QString &gw, Settings::RouteMode mode) IpcClient::Interface()->routeAddList(gw, ips); // re-resolve domains - for (const QString &site: sites) { - const auto &cbResolv = [this, site, gw, mode, ips](const QHostInfo &hostInfo){ - const QList &addresses = hostInfo.addresses(); - QString ipv4Addr; - for (const QHostAddress &addr: hostInfo.addresses()) { - if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) { - const QString &ip = addr.toString(); - //qDebug() << "VpnConnection::addSitesRoutes updating site" << site << ip; - if (!ips.contains(ip)) { - IpcClient::Interface()->routeAddList(gw, QStringList() << ip); - m_settings->addVpnSite(mode, site, ip); - } - flushDns(); - break; + for (const QString &site : sites) { + const auto &cbResolv = [this, site, gw, mode, ips](const QHostInfo &hostInfo) { + const QList &addresses = hostInfo.addresses(); + QString ipv4Addr; + for (const QHostAddress &addr : hostInfo.addresses()) { + if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) { + const QString &ip = addr.toString(); + // qDebug() << "VpnConnection::addSitesRoutes updating site" << site << ip; + if (!ips.contains(ip)) { + IpcClient::Interface()->routeAddList(gw, QStringList() << ip); + m_settings->addVpnSite(mode, site, ip); } + flushDns(); + break; } - }; - QHostInfo::lookupHost(site, this, cbResolv); + } + }; + QHostInfo::lookupHost(site, this, cbResolv); } #endif } @@ -172,8 +163,7 @@ void VpnConnection::addRoutes(const QStringList &ips) if (connectionState() == Vpn::ConnectionState::Connected && IpcClient::Interface()) { if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) { IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), ips); - } - else if (m_settings->routeMode() == Settings::VpnAllExceptSites) { + } else if (m_settings->routeMode() == Settings::VpnAllExceptSites) { IpcClient::Interface()->routeAddList(m_vpnProtocol->routeGateway(), ips); } } @@ -186,8 +176,7 @@ void VpnConnection::deleteRoutes(const QStringList &ips) if (connectionState() == Vpn::ConnectionState::Connected && IpcClient::Interface()) { if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) { IpcClient::Interface()->routeDeleteList(vpnProtocol()->vpnGateway(), ips); - } - else if (m_settings->routeMode() == Settings::VpnAllExceptSites) { + } else if (m_settings->routeMode() == Settings::VpnAllExceptSites) { IpcClient::Interface()->routeDeleteList(m_vpnProtocol->routeGateway(), ips); } } @@ -197,7 +186,8 @@ void VpnConnection::deleteRoutes(const QStringList &ips) void VpnConnection::flushDns() { #ifdef AMNEZIA_DESKTOP - if (IpcClient::Interface()) IpcClient::Interface()->flushDns(); + if (IpcClient::Interface()) + IpcClient::Interface()->flushDns(); #endif } @@ -213,18 +203,22 @@ ErrorCode VpnConnection::lastError() const QMap VpnConnection::getLastVpnConfig(const QJsonObject &containerConfig) { QMap configs; - for (Proto proto: ProtocolProps::allProtocols()) { + for (Proto proto : ProtocolProps::allProtocols()) { - QString cfg = containerConfig.value(ProtocolProps::protoToString(proto)).toObject().value(config_key::last_config).toString(); + QString cfg = containerConfig.value(ProtocolProps::protoToString(proto)) + .toObject() + .value(config_key::last_config) + .toString(); - if (!cfg.isEmpty()) configs.insert(proto, cfg); + if (!cfg.isEmpty()) + configs.insert(proto, cfg); } return configs; } -QString VpnConnection::createVpnConfigurationForProto(int serverIndex, - const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, Proto proto, - ErrorCode *errorCode) +QString VpnConnection::createVpnConfigurationForProto(int serverIndex, const ServerCredentials &credentials, + DockerContainer container, const QJsonObject &containerConfig, + Proto proto, ErrorCode *errorCode) { QMap lastVpnConfig = getLastVpnConfig(containerConfig); @@ -232,10 +226,8 @@ QString VpnConnection::createVpnConfigurationForProto(int serverIndex, if (lastVpnConfig.contains(proto)) { configData = lastVpnConfig.value(proto); configData = m_configurator->processConfigWithLocalSettings(serverIndex, container, proto, configData); - } - else { - configData = m_configurator->genVpnProtocolConfig(credentials, - container, containerConfig, proto, errorCode); + } else { + configData = m_configurator->genVpnProtocolConfig(credentials, container, containerConfig, proto, errorCode); if (errorCode && *errorCode) { return ""; @@ -246,7 +238,8 @@ QString VpnConnection::createVpnConfigurationForProto(int serverIndex, configData = m_configurator->processConfigWithLocalSettings(serverIndex, container, proto, configData); if (serverIndex >= 0) { - qDebug() << "VpnConnection::createVpnConfiguration: saving config for server #" << serverIndex << container << proto; + qDebug() << "VpnConnection::createVpnConfiguration: saving config for server #" << serverIndex << container + << proto; QJsonObject protoObject = m_settings->protocolConfig(serverIndex, container, proto); protoObject.insert(config_key::last_config, configDataBeforeLocalProcessing); m_settings->setProtocolConfig(serverIndex, container, proto, protoObject); @@ -256,17 +249,18 @@ QString VpnConnection::createVpnConfigurationForProto(int serverIndex, return configData; } -QJsonObject VpnConnection::createVpnConfiguration(int serverIndex, - const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, ErrorCode *errorCode) +QJsonObject VpnConnection::createVpnConfiguration(int serverIndex, const ServerCredentials &credentials, + DockerContainer container, const QJsonObject &containerConfig, + ErrorCode *errorCode) { QJsonObject vpnConfiguration; for (ProtocolEnumNS::Proto proto : ContainerProps::protocolsForContainer(container)) { - QJsonObject vpnConfigData = QJsonDocument::fromJson( - createVpnConfigurationForProto( - serverIndex, credentials, container, containerConfig, proto, errorCode).toUtf8()). - object(); + QJsonObject vpnConfigData = + QJsonDocument::fromJson(createVpnConfigurationForProto(serverIndex, credentials, container, + containerConfig, proto, errorCode) + .toUtf8()) + .object(); if (errorCode && *errorCode) { return {}; @@ -293,12 +287,14 @@ QJsonObject VpnConnection::createVpnConfiguration(int serverIndex, return vpnConfiguration; } -void VpnConnection::connectToVpn(int serverIndex, - const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig) +void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig) { qDebug() << QString("ConnectToVpn, Server index is %1, container is %2, route mode is") - .arg(serverIndex).arg(ContainerProps::containerToString(container)) << m_settings->routeMode(); -#if !defined (Q_OS_ANDROID) && !defined (Q_OS_IOS) + .arg(serverIndex) + .arg(ContainerProps::containerToString(container)) + << m_settings->routeMode(); +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) if (!m_IpcClient) { m_IpcClient = new IpcClient(this); } @@ -331,8 +327,8 @@ void VpnConnection::connectToVpn(int serverIndex, emit connectionStateChanged(Vpn::ConnectionState::Error); return; } - -#if !defined (Q_OS_ANDROID) && !defined (Q_OS_IOS) + +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) m_vpnProtocol.reset(VpnProtocol::factory(container, m_vpnConfiguration)); if (!m_vpnProtocol) { emit connectionStateChanged(Vpn::ConnectionState::Error); @@ -354,17 +350,21 @@ void VpnConnection::connectToVpn(int serverIndex, createProtocolConnections(); e = m_vpnProtocol.data()->start(); - if (e) emit connectionStateChanged(Vpn::ConnectionState::Error); + if (e) + emit connectionStateChanged(Vpn::ConnectionState::Error); } -void VpnConnection::createProtocolConnections() { +void VpnConnection::createProtocolConnections() +{ connect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError); - connect(m_vpnProtocol.data(), SIGNAL(connectionStateChanged(Vpn::ConnectionState)), this, SLOT(onConnectionStateChanged(Vpn::ConnectionState))); + connect(m_vpnProtocol.data(), SIGNAL(connectionStateChanged(Vpn::ConnectionState)), this, + SLOT(onConnectionStateChanged(Vpn::ConnectionState))); connect(m_vpnProtocol.data(), SIGNAL(bytesChanged(quint64, quint64)), this, SLOT(onBytesChanged(quint64, quint64))); } #ifdef Q_OS_ANDROID -void VpnConnection::restoreConnection() { +void VpnConnection::restoreConnection() +{ createAndroidConnections(); m_vpnProtocol.reset(androidVpnProtocol); @@ -384,11 +384,13 @@ void VpnConnection::createAndroidConnections(DockerContainer container) { androidVpnProtocol = createDefaultAndroidVpnProtocol(container); - connect(AndroidController::instance(), &AndroidController::connectionStateChanged, androidVpnProtocol, &AndroidVpnProtocol::setConnectionState); - connect(AndroidController::instance(), &AndroidController::statusUpdated, androidVpnProtocol, &AndroidVpnProtocol::connectionDataUpdated); + connect(AndroidController::instance(), &AndroidController::connectionStateChanged, androidVpnProtocol, + &AndroidVpnProtocol::setConnectionState); + connect(AndroidController::instance(), &AndroidController::statusUpdated, androidVpnProtocol, + &AndroidVpnProtocol::connectionDataUpdated); } -AndroidVpnProtocol* VpnConnection::createDefaultAndroidVpnProtocol(DockerContainer container) +AndroidVpnProtocol *VpnConnection::createDefaultAndroidVpnProtocol(DockerContainer container) { Proto proto = ContainerProps::defaultProtocol(container); AndroidVpnProtocol *androidVpnProtocol = new AndroidVpnProtocol(proto, m_vpnConfiguration); @@ -415,8 +417,6 @@ void VpnConnection::disconnectFromVpn() } #endif - if (!m_vpnProtocol.data()) { - emit connectionStateChanged(Vpn::ConnectionState::Disconnected); #ifdef Q_OS_ANDROID AndroidController::instance()->stop(); #endif @@ -427,7 +427,7 @@ void VpnConnection::disconnectFromVpn() #endif if (!m_vpnProtocol.data()) { - emit connectionStateChanged(VpnProtocol::Disconnected); + emit connectionStateChanged(Vpn::ConnectionState::Disconnected); return; } @@ -439,7 +439,8 @@ void VpnConnection::disconnectFromVpn() Vpn::ConnectionState VpnConnection::connectionState() { - if (!m_vpnProtocol) return Vpn::ConnectionState::Disconnected; + if (!m_vpnProtocol) + return Vpn::ConnectionState::Disconnected; return m_vpnProtocol->connectionState(); } From 810da0db615bf457ac817af8bc843962d1e399c7 Mon Sep 17 00:00:00 2001 From: pokamest Date: Mon, 28 Aug 2023 14:14:10 -0700 Subject: [PATCH 078/278] Fixes for iOS --- client/platforms/ios/ios_controller.h | 2 +- client/platforms/ios/ios_controller.mm | 28 ++++++++++---------- client/ui/controllers/settingsController.cpp | 4 +++ client/ui/controllers/sitesController.cpp | 4 +++ 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/client/platforms/ios/ios_controller.h b/client/platforms/ios/ios_controller.h index 0750f7cd..4d1122b2 100644 --- a/client/platforms/ios/ios_controller.h +++ b/client/platforms/ios/ios_controller.h @@ -47,7 +47,7 @@ public: void getBackendLogs(std::function &&callback); void checkStatus(); signals: - void connectionStateChanged(VpnProtocol::VpnConnectionState state); + void connectionStateChanged(Vpn::ConnectionState state); void bytesChanged(quint64 receivedBytes, quint64 sentBytes); protected slots: diff --git a/client/platforms/ios/ios_controller.mm b/client/platforms/ios/ios_controller.mm index c8e27252..6f23ac81 100644 --- a/client/platforms/ios/ios_controller.mm +++ b/client/platforms/ios/ios_controller.mm @@ -30,22 +30,22 @@ const char* MessageKey::host = "host"; const char* MessageKey::port = "port"; const char* MessageKey::isOnDemand = "is-on-demand"; -VpnProtocol::VpnConnectionState iosStatusToState(NEVPNStatus status) { +Vpn::ConnectionState iosStatusToState(NEVPNStatus status) { switch (status) { case NEVPNStatusInvalid: - return VpnProtocol::VpnConnectionState::Unknown; + return Vpn::ConnectionState::Unknown; case NEVPNStatusDisconnected: - return VpnProtocol::VpnConnectionState::Disconnected; + return Vpn::ConnectionState::Disconnected; case NEVPNStatusConnecting: - return VpnProtocol::VpnConnectionState::Connecting; + return Vpn::ConnectionState::Connecting; case NEVPNStatusConnected: - return VpnProtocol::VpnConnectionState::Connected; + return Vpn::ConnectionState::Connected; case NEVPNStatusReasserting: - return VpnProtocol::VpnConnectionState::Connecting; + return Vpn::ConnectionState::Connecting; case NEVPNStatusDisconnecting: - return VpnProtocol::VpnConnectionState::Disconnecting; + return Vpn::ConnectionState::Disconnecting; default: - return VpnProtocol::VpnConnectionState::Unknown; + return Vpn::ConnectionState::Unknown; } } @@ -85,7 +85,7 @@ bool IosController::initialize() @try { if (error) { qDebug() << "IosController::initialize : Error:" << [error.localizedDescription UTF8String]; - emit connectionStateChanged(VpnProtocol::VpnConnectionState::Error); + emit connectionStateChanged(Vpn::ConnectionState::Error); ok = false; return; } @@ -147,7 +147,7 @@ bool IosController::connectVpn(amnezia::Proto proto, const QJsonObject& configur @try { if (error) { qDebug() << "IosController::connectVpn : Error:" << [error.localizedDescription UTF8String]; - emit connectionStateChanged(VpnProtocol::VpnConnectionState::Error); + emit connectionStateChanged(Vpn::ConnectionState::Error); ok = false; return; } @@ -161,7 +161,7 @@ bool IosController::connectVpn(amnezia::Proto proto, const QJsonObject& configur m_currentTunnel = manager; qDebug() << "IosController::connectVpn : Using existing tunnel"; if (manager.connection.status == NEVPNStatusConnected) { - emit connectionStateChanged(VpnProtocol::VpnConnectionState::Connected); + emit connectionStateChanged(Vpn::ConnectionState::Connected); return; } @@ -356,7 +356,7 @@ void IosController::startTunnel() if (saveError) { qDebug() << "IosController::startOpenVPN : Connect OpenVPN Tunnel Save Error" << saveError.localizedDescription.UTF8String; - emit connectionStateChanged(VpnProtocol::VpnConnectionState::Error); + emit connectionStateChanged(Vpn::ConnectionState::Error); return; } @@ -365,7 +365,7 @@ void IosController::startTunnel() if (loadError) { qDebug() << "IosController::startOpenVPN : Connect OpenVPN Tunnel Load Error" << loadError.localizedDescription.UTF8String; - emit connectionStateChanged(VpnProtocol::VpnConnectionState::Error); + emit connectionStateChanged(Vpn::ConnectionState::Error); return; } @@ -389,7 +389,7 @@ void IosController::startTunnel() if (!started || startError) { qDebug() << "IosController::startOpenVPN : Connect OpenVPN Tunnel Start Error" << (startError ? startError.localizedDescription.UTF8String : ""); - emit connectionStateChanged(VpnProtocol::VpnConnectionState::Error); + emit connectionStateChanged(Vpn::ConnectionState::Error); } else { qDebug() << "IosController::startOpenVPN : Starting the tunnel succeeded"; } diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index 531d3c50..7d1bf643 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -2,6 +2,10 @@ #include +#ifdef Q_OS_IOS + #include +#endif + #include "fileUtilites.h" #include "logger.h" #include "ui/qautostart.h" diff --git a/client/ui/controllers/sitesController.cpp b/client/ui/controllers/sitesController.cpp index 891ddb6f..00704bca 100644 --- a/client/ui/controllers/sitesController.cpp +++ b/client/ui/controllers/sitesController.cpp @@ -3,6 +3,10 @@ #include #include +#ifdef Q_OS_IOS + #include +#endif + #include "fileUtilites.h" #include "utilities.h" From 0eda42f29fa9d49540fea3dd93da7935cef575f9 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Wed, 30 Aug 2023 01:17:14 +0300 Subject: [PATCH 079/278] Savefile for iOS --- client/fileUtilites.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/client/fileUtilites.cpp b/client/fileUtilites.cpp index 87fa7aed..94ef077e 100644 --- a/client/fileUtilites.cpp +++ b/client/fileUtilites.cpp @@ -1,4 +1,5 @@ #include "fileUtilites.h" +#include "platforms/ios/MobileUtils.h" #include #include @@ -6,9 +7,15 @@ void FileUtilites::saveFile(const QString &fileExtension, const QString &caption, const QString &fileName, const QString &data) { + +#ifdef Q_OS_IOS + QUrl fileUrl = QDir::tempPath() + "/" + fileName; +#else QString docDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); QUrl fileUrl = QFileDialog::getSaveFileUrl(nullptr, caption, QUrl::fromLocalFile(docDir + "/" + fileName), "*" + fileExtension); +#endif + if (fileUrl.isEmpty()) return; if (!fileUrl.toString().endsWith(fileExtension)) { @@ -17,13 +24,24 @@ void FileUtilites::saveFile(const QString &fileExtension, const QString &caption if (fileUrl.isEmpty()) return; +#ifdef Q_OS_IOS + QFile save(fileUrl.toString()); +#else QFile save(fileUrl.toLocalFile()); +#endif // todo check if save successful save.open(QIODevice::WriteOnly); save.write(data.toUtf8()); save.close(); +#ifdef Q_OS_IOS + QStringList filesToSend; + filesToSend.append(fileUrl.toString()); + MobileUtils::shareText(filesToSend); + return; +#endif + QFileInfo fi(fileUrl.toLocalFile()); QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); } From 1c1a82696b52d0f5a8d86923f7137a1d6e629998 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Wed, 30 Aug 2023 17:23:55 -0400 Subject: [PATCH 080/278] Fix qrDecoder for Android --- client/ui/qml/Pages2/PageSetupWizardConfigSource.qml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 45aad9d0..68544497 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -87,7 +87,9 @@ It's okay as long as it's from someone you trust.") clickedFunction: function() { ImportController.startDecodingQr() - goToPage(PageEnum.PageSetupWizardQrReader) + if (Qt.platform.os === "ios") { + goToPage(PageEnum.PageSetupWizardQrReader) + } } } From e8862a3811d7b0de79881f391bebb5ddbd617a4d Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 30 Aug 2023 15:10:44 +0500 Subject: [PATCH 081/278] removed the use of QFileDialog --- client/CMakeLists.txt | 23 ++++--- client/amnezia_application.h | 10 +-- client/core/servercontroller.cpp | 1 - client/fileUtilites.cpp | 69 +++++++++++++------ client/fileUtilites.h | 11 ++- client/platforms/ios/ios_controller.h | 2 +- client/ui/controllers/exportController.cpp | 31 +-------- client/ui/controllers/exportController.h | 2 +- client/ui/controllers/importController.cpp | 18 +---- client/ui/controllers/importController.h | 2 +- client/ui/controllers/settingsController.cpp | 32 ++------- client/ui/controllers/settingsController.h | 6 +- client/ui/controllers/sitesController.cpp | 27 ++------ client/ui/controllers/sitesController.h | 4 +- .../qml/Components/ShareConnectionDrawer.qml | 17 ++++- .../qml/Controls2/TextFieldWithHeaderType.qml | 10 +++ client/ui/qml/Pages2/PageSettingsBackup.qml | 42 +++++++++-- client/ui/qml/Pages2/PageSettingsLogging.qml | 18 ++++- .../qml/Pages2/PageSettingsSplitTunneling.qml | 44 +++++++++--- .../Pages2/PageSetupWizardConfigSource.qml | 13 +++- client/ui/uilogic.cpp | 4 +- client/vpnconnection.cpp | 2 +- 22 files changed, 224 insertions(+), 164 deletions(-) diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 713a9165..47264fe6 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -10,31 +10,34 @@ set_property(GLOBAL PROPERTY AUTOMOC_TARGETS_FOLDER "Autogen") set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "Autogen") set(PACKAGES - Widgets Core Gui Network Xml + Core Gui Network Xml RemoteObjects Quick Svg QuickControls2 Core5Compat Concurrent LinguistTools ) + if(IOS) - set(PACKAGES - ${PACKAGES} - Multimedia - ) + set(PACKAGES ${PACKAGES} Multimedia) +endif() + +if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) + set(PACKAGES ${PACKAGES} Widgets) endif() find_package(Qt6 REQUIRED COMPONENTS ${PACKAGES}) set(LIBS ${LIBS} - Qt6::Widgets Qt6::Core Qt6::Gui + Qt6::Core Qt6::Gui Qt6::Network Qt6::Xml Qt6::RemoteObjects Qt6::Quick Qt6::Svg Qt6::QuickControls2 Qt6::Core5Compat Qt6::Concurrent ) if(IOS) - set(LIBS - ${LIBS} - Qt6::Multimedia - ) + set(LIBS ${LIBS} Qt6::Multimedia) +endif() + +if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) + set(LIBS ${LIBS} Qt6::Widgets) endif() qt_standard_project_setup() diff --git a/client/amnezia_application.h b/client/amnezia_application.h index c4f33753..e18fb70c 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -1,13 +1,15 @@ #ifndef AMNEZIA_APPLICATION_H #define AMNEZIA_APPLICATION_H -#include -#include - #include #include #include #include +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) + #include +#else + #include +#endif #include "settings.h" #include "vpnconnection.h" @@ -39,7 +41,7 @@ #define amnApp (static_cast(QCoreApplication::instance())) #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) - #define AMNEZIA_BASE_CLASS QApplication + #define AMNEZIA_BASE_CLASS QGuiApp #else #define AMNEZIA_BASE_CLASS SingleApplication #define QAPPLICATION_CLASS QApplication diff --git a/client/core/servercontroller.cpp b/client/core/servercontroller.cpp index cbb87f11..0dad3020 100644 --- a/client/core/servercontroller.cpp +++ b/client/core/servercontroller.cpp @@ -1,6 +1,5 @@ #include "servercontroller.h" -#include #include #include #include diff --git a/client/fileUtilites.cpp b/client/fileUtilites.cpp index 87fa7aed..659243aa 100644 --- a/client/fileUtilites.cpp +++ b/client/fileUtilites.cpp @@ -3,35 +3,59 @@ #include #include -void FileUtilites::saveFile(const QString &fileExtension, const QString &caption, const QString &fileName, - const QString &data) -{ - QString docDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); - QUrl fileUrl = QFileDialog::getSaveFileUrl(nullptr, caption, QUrl::fromLocalFile(docDir + "/" + fileName), - "*" + fileExtension); - if (fileUrl.isEmpty()) - return; - if (!fileUrl.toString().endsWith(fileExtension)) { - fileUrl = QUrl(fileUrl.toString() + fileExtension); - } - if (fileUrl.isEmpty()) - return; +#ifdef Q_OS_ANDROID + #include "platforms/android/android_controller.h" +#endif - QFile save(fileUrl.toLocalFile()); +#ifdef Q_OS_IOS + #include "platforms/ios/MobileUtils.h" + #include +#endif + +void FileUtilites::saveFile(QString fileName, const QString &data) +{ +#if defined Q_OS_ANDROID + AndroidController::instance()->shareConfig(data, fileName); + return; +#endif + +#ifdef Q_OS_IOS + QFile file(fileName); +#else + QUrl fileUrl = QUrl(fileName); + QFile file(fileUrl.toLocalFile()); +#endif // todo check if save successful - save.open(QIODevice::WriteOnly); - save.write(data.toUtf8()); - save.close(); + file.open(QIODevice::WriteOnly); + file.write(data.toUtf8()); + file.close(); + +#ifdef Q_OS_IOS + QStringList filesToSend; + filesToSend.append(fileName); + MobileUtils::shareText(filesToSend); + return; +#endif QFileInfo fi(fileUrl.toLocalFile()); QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); } -QString FileUtilites::getFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, - QString *selectedFilter, QFileDialog::Options options) +QString FileUtilites::getFileName(QString fileName) { - QString fileName = QFileDialog::getOpenFileName(parent, caption, dir, filter, selectedFilter, options); +#ifdef Q_OS_IOS + CFURLRef url = CFURLCreateWithFileSystemPath( + kCFAllocatorDefault, + CFStringCreateWithCharacters(0, reinterpret_cast(fileName.unicode()), fileName.length()), + kCFURLPOSIXPathStyle, 0); + + if (!CFURLStartAccessingSecurityScopedResource(url)) { + qDebug() << "Could not access path " << QUrl::fromLocalFile(fileName).toString(); + } + + return fileName; +#endif #ifdef Q_OS_ANDROID // patch for files containing spaces etc @@ -42,6 +66,9 @@ QString FileUtilites::getFileName(QWidget *parent, const QString &caption, const rawUrl.replace(" ", "%20"); fileName = contentUrl + sep + rawUrl; } -#endif + return fileName; +#endif + + return QUrl(FileUtilites::getFileName(fileName)).toLocalFile(); } diff --git a/client/fileUtilites.h b/client/fileUtilites.h index 8cf4807c..21cb03a9 100644 --- a/client/fileUtilites.h +++ b/client/fileUtilites.h @@ -1,19 +1,16 @@ #ifndef FILEUTILITES_H #define FILEUTILITES_H -#include +#include +#include class FileUtilites : public QObject { Q_OBJECT public: - static void saveFile(const QString &fileExtension, const QString &caption, const QString &fileName, - const QString &data); - - static QString getFileName(QWidget *parent = nullptr, const QString &caption = QString(), - const QString &dir = QString(), const QString &filter = QString(), - QString *selectedFilter = nullptr, QFileDialog::Options options = QFileDialog::Options()); + static void saveFile(QString fileName, const QString &data); + static QString getFileName(QString fileName); }; #endif // FILEUTILITES_H diff --git a/client/platforms/ios/ios_controller.h b/client/platforms/ios/ios_controller.h index 0750f7cd..4d1122b2 100644 --- a/client/platforms/ios/ios_controller.h +++ b/client/platforms/ios/ios_controller.h @@ -47,7 +47,7 @@ public: void getBackendLogs(std::function &&callback); void checkStatus(); signals: - void connectionStateChanged(VpnProtocol::VpnConnectionState state); + void connectionStateChanged(Vpn::ConnectionState state); void bytesChanged(quint64 receivedBytes, quint64 sentBytes); protected slots: diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index 4cee1dc9..ddc976cc 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -13,7 +13,6 @@ #include "core/errorstrings.h" #include "fileUtilites.h" #ifdef Q_OS_ANDROID - #include "platforms/android/android_controller.h" #include "platforms/android/androidutils.h" #endif #include "qrcodegen.hpp" @@ -201,35 +200,9 @@ QList ExportController::getQrCodes() return m_qrCodes; } -void ExportController::saveFile(const QString &fileExtension, const QString &caption, const QString &fileName) +void ExportController::saveFile(const QString &fileName) { -#if defined Q_OS_IOS -// ext.replace("*", ""); -// QString fileName = QDir::tempPath() + "/" + suggestedName; -// -// if (fileName.isEmpty()) -// return; -// if (!fileName.endsWith(ext)) -// fileName.append(ext); -// -// QFile::remove(fileName); -// -// QFile save(fileName); -// save.open(QIODevice::WriteOnly); -// save.write(data.toUtf8()); -// save.close(); -// -// QStringList filesToSend; -// filesToSend.append(fileName); -// MobileUtils::shareText(filesToSend); -// return; -#endif -#if defined Q_OS_ANDROID - AndroidController::instance()->shareConfig(m_config, "amnezia_config"); - return; -#endif - - FileUtilites::saveFile(fileExtension, caption, fileName, m_config); + FileUtilites::saveFile(fileName, m_config); } QList ExportController::generateQrCodeImageSeries(const QByteArray &data) diff --git a/client/ui/controllers/exportController.h b/client/ui/controllers/exportController.h index 913dfc3a..b526521e 100644 --- a/client/ui/controllers/exportController.h +++ b/client/ui/controllers/exportController.h @@ -35,7 +35,7 @@ public slots: QString getConfig(); QList getQrCodes(); - void saveFile(const QString &fileExtension, const QString &caption, const QString &fileName); + void saveFile(const QString &fileName); signals: void generateConfig(int type); diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 7f8b82a9..9a374aa1 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -85,23 +85,9 @@ ImportController::ImportController(const QSharedPointer &serversMo #endif } -void ImportController::extractConfigFromFile() +void ImportController::extractConfigFromFile(const QString &fileName) { - QString fileName = FileUtilites::getFileName(Q_NULLPTR, tr("Open config file"), - QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), - "*.vpn *.ovpn *.conf"); - QFile file(fileName); - -#ifdef Q_OS_IOS - CFURLRef url = CFURLCreateWithFileSystemPath( - kCFAllocatorDefault, - CFStringCreateWithCharacters(0, reinterpret_cast(fileName.unicode()), fileName.length()), - kCFURLPOSIXPathStyle, 0); - - if (!CFURLStartAccessingSecurityScopedResource(url)) { - qDebug() << "Could not access path " << QUrl::fromLocalFile(fileName).toString(); - } -#endif + QFile file(FileUtilites::getFileName(fileName)); if (file.open(QIODevice::ReadOnly)) { QString data = file.readAll(); diff --git a/client/ui/controllers/importController.h b/client/ui/controllers/importController.h index cccdf4b7..7def7733 100644 --- a/client/ui/controllers/importController.h +++ b/client/ui/controllers/importController.h @@ -21,7 +21,7 @@ public: public slots: void importConfig(); - void extractConfigFromFile(); + void extractConfigFromFile(const QString &fileName); void extractConfigFromData(QString &data); void extractConfigFromCode(QString code); bool extractConfigFromQr(const QByteArray &data); diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index 531d3c50..7c7402e0 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -68,9 +68,9 @@ void SettingsController::openLogsFolder() Logger::openLogsFolder(); } -void SettingsController::exportLogsFile() +void SettingsController::exportLogsFile(const QString &fileName) { - FileUtilites::saveFile(".log", tr("Save log"), "AmneziaVPN", Logger::getLogFile()); + FileUtilites::saveFile(fileName, Logger::getLogFile()); } void SettingsController::clearLogs() @@ -79,35 +79,17 @@ void SettingsController::clearLogs() Logger::clearServiceLogs(); } -void SettingsController::backupAppConfig() +void SettingsController::backupAppConfig(const QString &fileName) { - FileUtilites::saveFile(".backup", tr("Backup application config"), "AmneziaVPN", m_settings->backupAppConfig()); + FileUtilites::saveFile(fileName, m_settings->backupAppConfig()); } -void SettingsController::restoreAppConfig() +void SettingsController::restoreAppConfig(const QString &fileName) { - QString fileName = - FileUtilites::getFileName(Q_NULLPTR, tr("Open backup"), - QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.backup"); - - if (fileName.isEmpty()) { - return; - } - - QFile file(fileName); - -#ifdef Q_OS_IOS - CFURLRef url = CFURLCreateWithFileSystemPath( - kCFAllocatorDefault, - CFStringCreateWithCharacters(0, reinterpret_cast(fileName.unicode()), fileName.length()), - kCFURLPOSIXPathStyle, 0); - - if (!CFURLStartAccessingSecurityScopedResource(url)) { - qDebug() << "Could not access path " << QUrl::fromLocalFile(fileName).toString(); - } -#endif + QFile file(FileUtilites::getFileName(fileName)); file.open(QIODevice::ReadOnly); + QByteArray data = file.readAll(); bool ok = m_settings->restoreAppConfig(data); diff --git a/client/ui/controllers/settingsController.h b/client/ui/controllers/settingsController.h index 3ad602b7..af816d46 100644 --- a/client/ui/controllers/settingsController.h +++ b/client/ui/controllers/settingsController.h @@ -34,11 +34,11 @@ public slots: void toggleLogging(bool enable); void openLogsFolder(); - void exportLogsFile(); + void exportLogsFile(const QString &fileName); void clearLogs(); - void backupAppConfig(); - void restoreAppConfig(); + void backupAppConfig(const QString &fileName); + void restoreAppConfig(const QString &fileName); QString getAppVersion(); diff --git a/client/ui/controllers/sitesController.cpp b/client/ui/controllers/sitesController.cpp index 891ddb6f..9eafe6ba 100644 --- a/client/ui/controllers/sitesController.cpp +++ b/client/ui/controllers/sitesController.cpp @@ -79,28 +79,9 @@ void SitesController::removeSite(int index) emit finished(tr("Site removed: ") + hostname); } -void SitesController::importSites(bool replaceExisting) +void SitesController::importSites(const QString &fileName, bool replaceExisting) { - QString fileName = - FileUtilites::getFileName(Q_NULLPTR, tr("Open sites file"), - QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.json"); - - if (fileName.isEmpty()) { - return; - } - - QFile file(fileName); - -#ifdef Q_OS_IOS - CFURLRef url = CFURLCreateWithFileSystemPath( - kCFAllocatorDefault, - CFStringCreateWithCharacters(0, reinterpret_cast(fileName.unicode()), fileName.length()), - kCFURLPOSIXPathStyle, 0); - - if (!CFURLStartAccessingSecurityScopedResource(url)) { - qDebug() << "Could not access path " << QUrl::fromLocalFile(fileName).toString(); - } -#endif + QFile file(FileUtilites::getFileName(fileName)); if (!file.open(QIODevice::ReadOnly)) { emit errorOccurred(tr("Can't open file: ") + fileName); @@ -149,7 +130,7 @@ void SitesController::importSites(bool replaceExisting) emit finished(tr("Import completed")); } -void SitesController::exportSites() +void SitesController::exportSites(const QString &fileName) { auto sites = m_sitesModel->getCurrentSites(); @@ -163,7 +144,7 @@ void SitesController::exportSites() QJsonDocument jsonDocument(jsonArray); QByteArray jsonData = jsonDocument.toJson(); - FileUtilites::saveFile(".json", tr("Export sites file"), "sites", jsonData); + FileUtilites::saveFile(fileName, jsonData); emit finished(tr("Export completed")); } diff --git a/client/ui/controllers/sitesController.h b/client/ui/controllers/sitesController.h index ff78c3de..2171e7b4 100644 --- a/client/ui/controllers/sitesController.h +++ b/client/ui/controllers/sitesController.h @@ -19,8 +19,8 @@ public slots: void addSite(QString hostname); void removeSite(int index); - void importSites(bool replaceExisting); - void exportSites(); + void importSites(const QString &fileName, bool replaceExisting); + void exportSites(const QString &fileName); signals: void errorOccurred(const QString &errorMessage); diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index c369af42..41dd2ab2 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -3,6 +3,8 @@ import QtQuick.Controls import QtQuick.Layouts import QtQuick.Dialogs +import QtCore + import SortFilterProxyModel 0.2 import PageEnum 1.0 @@ -67,8 +69,19 @@ DrawerType { text: qsTr("Share") imageSource: "qrc:/images/controls/share-2.svg" - onClicked: { - ExportController.saveFile(configExtension, configCaption, configFileName) + onClicked: fileDialog.open() + + FileDialog { + id: fileDialog + acceptLabel: configCaption + nameFilters: [ "Config files (*" + configExtension + ")" ] + fileMode: FileDialog.SaveFile + + currentFile: StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/" + configFileName + defaultSuffix: configExtension + onAccepted: { + ExportController.saveFile(fileDialog.currentFile.toString()) + } } } diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml index 048a564d..3f80428e 100644 --- a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -146,4 +146,14 @@ Item { color: "#EB5757" } } + + MouseArea { + anchors.fill: root + cursorShape: Qt.PointingHandCursor + + onPressed: function(mouse) { + textField.forceActiveFocus() + mouse.accepted = false + } + } } diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml index 39cad732..363bc66f 100644 --- a/client/ui/qml/Pages2/PageSettingsBackup.qml +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -1,6 +1,9 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import QtQuick.Dialogs + +import QtCore import PageEnum 1.0 @@ -74,14 +77,36 @@ PageType { } BasicButtonType { + id: makeBackupButton Layout.fillWidth: true Layout.topMargin: 14 text: qsTr("Make a backup") onClicked: { + if (GC.isMobile()) { + backupAppConfig("AmneziaVPN.backup") + } else { + saveFileDialog.open() + } + } + + FileDialog { + id: saveFileDialog + acceptLabel: qsTr("Save backup file") + nameFilters: [ "Backup files (*.backup)" ] + fileMode: FileDialog.SaveFile + + currentFile: StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN" + defaultSuffix: ".backup" + onAccepted: { + makeBackupButton.backupAppConfig(saveFileDialog.currentFile.toString()) + } + } + + function backupAppConfig(fileName) { PageController.showBusyIndicator(true) - SettingsController.backupAppConfig() + SettingsController.backupAppConfig(fileName) PageController.showBusyIndicator(false) } } @@ -100,9 +125,18 @@ PageType { text: qsTr("Restore from backup") onClicked: { - PageController.showBusyIndicator(true) - SettingsController.restoreAppConfig() - PageController.showBusyIndicator(false) + openFileDialog.open() + } + + FileDialog { + id: openFileDialog + acceptLabel: qsTr("Open backup file") + nameFilters: [ "Backup files (*.backup)" ] + onAccepted: { + PageController.showBusyIndicator(true) + SettingsController.restoreAppConfig(openFileDialog.selectedFile.toString()) + PageController.showBusyIndicator(false) + } } } } diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml index e14be439..cfe7d7c9 100644 --- a/client/ui/qml/Pages2/PageSettingsLogging.qml +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -1,6 +1,9 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import QtQuick.Dialogs + +import QtCore import PageEnum 1.0 @@ -97,7 +100,20 @@ PageType { image: "qrc:/images/controls/save.svg" - onClicked: SettingsController.exportLogsFile() + onClicked: fileDialog.open() + + FileDialog { + id: fileDialog + acceptLabel: qsTr("Save logs") + nameFilters: [ "Logs files (*.log)" ] + fileMode: FileDialog.SaveFile + + currentFile: StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN" + defaultSuffix: ".log" + onAccepted: { + ExportController.saveFile(fileDialog.currentFile.toString()) + } + } } CaptionTextType { diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index 71f2ba29..535ab18c 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -1,6 +1,9 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts +import QtQuick.Dialogs + +import QtCore import SortFilterProxyModel 0.2 @@ -41,6 +44,8 @@ PageType { allExceptSites ] + property bool replaceExistingSites + QtObject { id: onlyForwardSites property string name: qsTr("Only the addresses in the list must be opened via VPN") @@ -295,8 +300,21 @@ PageType { text: qsTr("Save site list") clickedFunction: function() { - SitesController.exportSites() - moreActionsDrawer.close() + saveFileDialog.open() + } + + FileDialog { + id: saveFileDialog + acceptLabel: qsTr("Save sites") + nameFilters: [ "Sites files (*.json)" ] + fileMode: FileDialog.SaveFile + + currentFile: StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/sites" + defaultSuffix: ".json" + onAccepted: { + SitesController.exportSites(saveFileDialog.currentFile.toString()) + moreActionsDrawer.close() + } } } @@ -331,6 +349,7 @@ PageType { anchors.bottom: parent.bottom contentHeight: importSitesDrawerContent.height + ColumnLayout { id: importSitesDrawerContent @@ -351,9 +370,8 @@ PageType { text: qsTr("Replace site list") clickedFunction: function() { - SitesController.importSites(true) - importSitesDrawer.close() - moreActionsDrawer.close() + root.replaceExistingSites = true + openFileDialog.open() } } @@ -364,13 +382,23 @@ PageType { text: qsTr("Add imported sites to existing ones") clickedFunction: function() { - SitesController.importSites(false) - importSitesDrawer.close() - moreActionsDrawer.close() + root.replaceExistingSites = false + openFileDialog.open() } } DividerType {} + + FileDialog { + id: openFileDialog + acceptLabel: qsTr("Open sites file") + nameFilters: [ "Sites files (*.json)" ] + onAccepted: { + SitesController.importSites(openFileDialog.selectedFile.toString(), replaceExistingSites) + importSitesDrawer.close() + moreActionsDrawer.close() + } + } } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 45aad9d0..6986509d 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -70,8 +70,17 @@ It's okay as long as it's from someone you trust.") leftImageSource: "qrc:/images/controls/folder-open.svg" clickedFunction: function() { - ImportController.extractConfigFromFile() - goToPage(PageEnum.PageSetupWizardViewConfig) + fileDialog.open() + } + + FileDialog { + id: fileDialog + acceptLabel: qsTr("Open config file") + nameFilters: [ "Config files (*.vpn *.ovpn *.conf)" ] + onAccepted: { + ImportController.extractConfigFromFile(fileDialog.selectedFile.toString()) + goToPage(PageEnum.PageSetupWizardViewConfig) + } } } diff --git a/client/ui/uilogic.cpp b/client/ui/uilogic.cpp index 546b18cb..e7d90f85 100644 --- a/client/ui/uilogic.cpp +++ b/client/ui/uilogic.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -175,7 +174,8 @@ void UiLogic::showOnStartup() void UiLogic::onUpdateAllPages() { for (auto logic : m_logicMap) { - if (dynamic_cast(logic) || dynamic_cast(logic) || dynamic_cast(logic)) { + if (dynamic_cast(logic) || dynamic_cast(logic) + || dynamic_cast(logic)) { continue; } logic->onUpdatePage(); diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index 1e8fd60a..1cff01e6 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -1,5 +1,5 @@ #include "qtimer.h" -#include + #include #include #include From 4baa003c0dc4995ba7dfacb898ff11b1dfe34280 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 31 Aug 2023 16:00:41 +0500 Subject: [PATCH 082/278] removed old ui files --- client/CMakeLists.txt | 2 - client/amnezia_application.cpp | 2 +- client/amnezia_application.h | 2 +- client/configurators/ikev2_configurator.cpp | 48 +- client/configurators/openvpn_configurator.cpp | 128 ++-- client/configurators/ssh_configurator.cpp | 59 +- .../configurators/wireguard_configurator.cpp | 98 +-- client/fileUtilites.cpp | 4 +- client/platforms/android/androidutils.cpp | 81 +-- .../platforms/android/androidvpnactivity.cpp | 112 ++-- client/resources.qrc | 73 --- .../ui/controllers/connectionController.cpp | 6 +- client/ui/controllers/pageController.cpp | 7 +- client/ui/controllers/settingsController.cpp | 4 - client/ui/controllers/sitesController.cpp | 5 +- .../AdvancedServerSettingsLogic.cpp | 88 --- .../pages_logic/AdvancedServerSettingsLogic.h | 31 - client/ui/pages_logic/AppSettingsLogic.cpp | 119 ---- client/ui/pages_logic/AppSettingsLogic.h | 37 -- client/ui/pages_logic/ClientInfoLogic.cpp | 213 ------ client/ui/pages_logic/ClientInfoLogic.h | 42 -- .../ui/pages_logic/ClientManagementLogic.cpp | 143 ---- client/ui/pages_logic/ClientManagementLogic.h | 33 - .../ui/pages_logic/GeneralSettingsLogic.cpp | 40 -- client/ui/pages_logic/GeneralSettingsLogic.h | 25 - .../ui/pages_logic/NetworkSettingsLogic.cpp | 52 -- client/ui/pages_logic/NetworkSettingsLogic.h | 35 - .../pages_logic/NewServerProtocolsLogic.cpp | 34 - .../ui/pages_logic/NewServerProtocolsLogic.h | 25 - client/ui/pages_logic/PageLogicBase.cpp | 16 - client/ui/pages_logic/PageLogicBase.h | 35 - client/ui/pages_logic/QrDecoderLogic.cpp | 125 ---- client/ui/pages_logic/QrDecoderLogic.h | 41 -- .../ServerConfiguringProgressLogic.cpp | 187 ------ .../ServerConfiguringProgressLogic.h | 73 --- .../ui/pages_logic/ServerContainersLogic.cpp | 128 ---- client/ui/pages_logic/ServerContainersLogic.h | 29 - client/ui/pages_logic/ServerListLogic.cpp | 47 -- client/ui/pages_logic/ServerListLogic.h | 30 - client/ui/pages_logic/ServerSettingsLogic.cpp | 144 ----- client/ui/pages_logic/ServerSettingsLogic.h | 62 -- .../ui/pages_logic/ShareConnectionLogic.cpp | 293 --------- client/ui/pages_logic/ShareConnectionLogic.h | 54 -- client/ui/pages_logic/SitesLogic.cpp | 211 ------ client/ui/pages_logic/SitesLogic.h | 33 - client/ui/pages_logic/StartPageLogic.cpp | 374 ----------- client/ui/pages_logic/StartPageLogic.h | 56 -- client/ui/pages_logic/ViewConfigLogic.cpp | 93 --- client/ui/pages_logic/ViewConfigLogic.h | 47 -- client/ui/pages_logic/VpnLogic.cpp | 245 ------- client/ui/pages_logic/VpnLogic.h | 70 -- client/ui/pages_logic/WizardLogic.cpp | 70 -- client/ui/pages_logic/WizardLogic.h | 31 - .../ui/pages_logic/protocols/CloakLogic.cpp | 137 ---- client/ui/pages_logic/protocols/CloakLogic.h | 46 -- .../ui/pages_logic/protocols/OpenVpnLogic.cpp | 203 ------ .../ui/pages_logic/protocols/OpenVpnLogic.h | 63 -- .../protocols/OtherProtocolsLogic.cpp | 169 ----- .../protocols/OtherProtocolsLogic.h | 45 -- .../protocols/PageProtocolLogicBase.cpp | 8 - .../protocols/PageProtocolLogicBase.h | 24 - .../protocols/ShadowSocksLogic.cpp | 127 ---- .../pages_logic/protocols/ShadowSocksLogic.h | 44 -- .../pages_logic/protocols/WireGuardLogic.cpp | 30 - .../ui/pages_logic/protocols/WireGuardLogic.h | 26 - client/ui/qml/Controls/BackButton.qml | 33 - client/ui/qml/Controls/BasicButtonType.qml | 17 - client/ui/qml/Controls/BlueButtonType.qml | 28 - client/ui/qml/Controls/Caption.qml | 17 - client/ui/qml/Controls/CheckBoxType.qml | 27 - client/ui/qml/Controls/ComboBoxType.qml | 11 - client/ui/qml/Controls/ContextMenu.qml | 33 - client/ui/qml/Controls/FadeBehavior.qml | 35 - client/ui/qml/Controls/FlickableType.qml | 26 - client/ui/qml/Controls/ImageButtonType.qml | 17 - client/ui/qml/Controls/LabelType.qml | 17 - client/ui/qml/Controls/Logo.qml | 8 - client/ui/qml/Controls/PopupWarning.qml | 34 - client/ui/qml/Controls/PopupWithQuestion.qml | 62 -- client/ui/qml/Controls/PopupWithTextField.qml | 62 -- client/ui/qml/Controls/RadioButtonType.qml | 36 -- client/ui/qml/Controls/RichLabelType.qml | 17 - client/ui/qml/Controls/SettingButtonType.qml | 33 - .../ShareConnectionButtonCopyType.qml | 26 - .../Controls/ShareConnectionButtonType.qml | 27 - .../qml/Controls/ShareConnectionContent.qml | 65 -- client/ui/qml/Controls/SvgButtonType.qml | 16 - client/ui/qml/Controls/SvgImageType.qml | 23 - client/ui/qml/Controls/TextAreaType.qml | 63 -- client/ui/qml/Controls/TextFieldType.qml | 52 -- client/ui/qml/Controls/UrlButtonType.qml | 25 - client/ui/qml/Controls/VisibleBehavior.qml | 6 - .../Pages/ClientInfo/PageClientInfoBase.qml | 15 - .../ClientInfo/PageClientInfoOpenVPN.qml | 115 ---- .../ClientInfo/PageClientInfoWireGuard.qml | 100 --- .../InstallSettings/InstallSettingsBase.qml | 75 --- .../Pages/InstallSettings/SelectContainer.qml | 200 ------ client/ui/qml/Pages/PageAbout.qml | 90 --- .../qml/Pages/PageAdvancedServerSettings.qml | 118 ---- client/ui/qml/Pages/PageAppSetting.qml | 152 ----- client/ui/qml/Pages/PageBase.qml | 20 - client/ui/qml/Pages/PageClientManagement.qml | 119 ---- client/ui/qml/Pages/PageGeneralSettings.qml | 166 ----- client/ui/qml/Pages/PageNetworkSetting.qml | 113 ---- client/ui/qml/Pages/PageNewServer.qml | 61 -- .../ui/qml/Pages/PageNewServerProtocols.qml | 154 ----- client/ui/qml/Pages/PageQrDecoderIos.qml | 94 --- .../Pages/PageServerConfiguringProgress.qml | 121 ---- client/ui/qml/Pages/PageServerContainers.qml | 434 ------------- client/ui/qml/Pages/PageServerList.qml | 185 ------ client/ui/qml/Pages/PageServerSettings.qml | 140 ---- client/ui/qml/Pages/PageSetupWizard.qml | 108 ---- .../ui/qml/Pages/PageSetupWizardHighLevel.qml | 96 --- .../ui/qml/Pages/PageSetupWizardLowLevel.qml | 65 -- .../qml/Pages/PageSetupWizardMediumLevel.qml | 60 -- .../ui/qml/Pages/PageSetupWizardVPNMode.qml | 65 -- client/ui/qml/Pages/PageShareConnection.qml | 87 --- client/ui/qml/Pages/PageSites.qml | 315 --------- client/ui/qml/Pages/PageStart.qml | 356 ---------- client/ui/qml/Pages/PageVPN.qml | 387 ----------- client/ui/qml/Pages/PageViewConfig.qml | 138 ---- .../ui/qml/Pages/Protocols/PageProtoCloak.qml | 194 ------ .../qml/Pages/Protocols/PageProtoOpenVPN.qml | 454 ------------- .../ui/qml/Pages/Protocols/PageProtoSftp.qml | 140 ---- .../Pages/Protocols/PageProtoShadowSocks.qml | 175 ----- .../Pages/Protocols/PageProtoTorWebSite.qml | 69 -- .../Pages/Protocols/PageProtoWireGuard.qml | 60 -- .../qml/Pages/Protocols/PageProtocolBase.qml | 13 - .../qml/Pages/Share/PageShareProtoAmnezia.qml | 145 ----- .../qml/Pages/Share/PageShareProtoCloak.qml | 99 --- .../qml/Pages/Share/PageShareProtoIkev2.qml | 131 ---- .../qml/Pages/Share/PageShareProtoOpenVPN.qml | 96 --- .../ui/qml/Pages/Share/PageShareProtoSftp.qml | 21 - .../Pages/Share/PageShareProtoShadowSocks.qml | 115 ---- .../Pages/Share/PageShareProtoTorWebSite.qml | 20 - .../Pages/Share/PageShareProtoWireGuard.qml | 103 --- .../qml/Pages/Share/PageShareProtocolBase.qml | 19 - client/ui/qml/main.qml | 387 ----------- client/ui/uilogic.cpp | 609 ------------------ client/ui/uilogic.h | 201 ------ 140 files changed, 285 insertions(+), 12695 deletions(-) delete mode 100644 client/ui/pages_logic/AdvancedServerSettingsLogic.cpp delete mode 100644 client/ui/pages_logic/AdvancedServerSettingsLogic.h delete mode 100644 client/ui/pages_logic/AppSettingsLogic.cpp delete mode 100644 client/ui/pages_logic/AppSettingsLogic.h delete mode 100644 client/ui/pages_logic/ClientInfoLogic.cpp delete mode 100644 client/ui/pages_logic/ClientInfoLogic.h delete mode 100644 client/ui/pages_logic/ClientManagementLogic.cpp delete mode 100644 client/ui/pages_logic/ClientManagementLogic.h delete mode 100644 client/ui/pages_logic/GeneralSettingsLogic.cpp delete mode 100644 client/ui/pages_logic/GeneralSettingsLogic.h delete mode 100644 client/ui/pages_logic/NetworkSettingsLogic.cpp delete mode 100644 client/ui/pages_logic/NetworkSettingsLogic.h delete mode 100644 client/ui/pages_logic/NewServerProtocolsLogic.cpp delete mode 100644 client/ui/pages_logic/NewServerProtocolsLogic.h delete mode 100644 client/ui/pages_logic/PageLogicBase.cpp delete mode 100644 client/ui/pages_logic/PageLogicBase.h delete mode 100644 client/ui/pages_logic/QrDecoderLogic.cpp delete mode 100644 client/ui/pages_logic/QrDecoderLogic.h delete mode 100644 client/ui/pages_logic/ServerConfiguringProgressLogic.cpp delete mode 100644 client/ui/pages_logic/ServerConfiguringProgressLogic.h delete mode 100644 client/ui/pages_logic/ServerContainersLogic.cpp delete mode 100644 client/ui/pages_logic/ServerContainersLogic.h delete mode 100644 client/ui/pages_logic/ServerListLogic.cpp delete mode 100644 client/ui/pages_logic/ServerListLogic.h delete mode 100644 client/ui/pages_logic/ServerSettingsLogic.cpp delete mode 100644 client/ui/pages_logic/ServerSettingsLogic.h delete mode 100644 client/ui/pages_logic/ShareConnectionLogic.cpp delete mode 100644 client/ui/pages_logic/ShareConnectionLogic.h delete mode 100644 client/ui/pages_logic/SitesLogic.cpp delete mode 100644 client/ui/pages_logic/SitesLogic.h delete mode 100644 client/ui/pages_logic/StartPageLogic.cpp delete mode 100644 client/ui/pages_logic/StartPageLogic.h delete mode 100644 client/ui/pages_logic/ViewConfigLogic.cpp delete mode 100644 client/ui/pages_logic/ViewConfigLogic.h delete mode 100644 client/ui/pages_logic/VpnLogic.cpp delete mode 100644 client/ui/pages_logic/VpnLogic.h delete mode 100644 client/ui/pages_logic/WizardLogic.cpp delete mode 100644 client/ui/pages_logic/WizardLogic.h delete mode 100644 client/ui/pages_logic/protocols/CloakLogic.cpp delete mode 100644 client/ui/pages_logic/protocols/CloakLogic.h delete mode 100644 client/ui/pages_logic/protocols/OpenVpnLogic.cpp delete mode 100644 client/ui/pages_logic/protocols/OpenVpnLogic.h delete mode 100644 client/ui/pages_logic/protocols/OtherProtocolsLogic.cpp delete mode 100644 client/ui/pages_logic/protocols/OtherProtocolsLogic.h delete mode 100644 client/ui/pages_logic/protocols/PageProtocolLogicBase.cpp delete mode 100644 client/ui/pages_logic/protocols/PageProtocolLogicBase.h delete mode 100644 client/ui/pages_logic/protocols/ShadowSocksLogic.cpp delete mode 100644 client/ui/pages_logic/protocols/ShadowSocksLogic.h delete mode 100644 client/ui/pages_logic/protocols/WireGuardLogic.cpp delete mode 100644 client/ui/pages_logic/protocols/WireGuardLogic.h delete mode 100644 client/ui/qml/Controls/BackButton.qml delete mode 100644 client/ui/qml/Controls/BasicButtonType.qml delete mode 100644 client/ui/qml/Controls/BlueButtonType.qml delete mode 100644 client/ui/qml/Controls/Caption.qml delete mode 100644 client/ui/qml/Controls/CheckBoxType.qml delete mode 100644 client/ui/qml/Controls/ComboBoxType.qml delete mode 100644 client/ui/qml/Controls/ContextMenu.qml delete mode 100644 client/ui/qml/Controls/FadeBehavior.qml delete mode 100644 client/ui/qml/Controls/FlickableType.qml delete mode 100644 client/ui/qml/Controls/ImageButtonType.qml delete mode 100644 client/ui/qml/Controls/LabelType.qml delete mode 100644 client/ui/qml/Controls/Logo.qml delete mode 100644 client/ui/qml/Controls/PopupWarning.qml delete mode 100644 client/ui/qml/Controls/PopupWithQuestion.qml delete mode 100644 client/ui/qml/Controls/PopupWithTextField.qml delete mode 100644 client/ui/qml/Controls/RadioButtonType.qml delete mode 100644 client/ui/qml/Controls/RichLabelType.qml delete mode 100644 client/ui/qml/Controls/SettingButtonType.qml delete mode 100644 client/ui/qml/Controls/ShareConnectionButtonCopyType.qml delete mode 100644 client/ui/qml/Controls/ShareConnectionButtonType.qml delete mode 100644 client/ui/qml/Controls/ShareConnectionContent.qml delete mode 100644 client/ui/qml/Controls/SvgButtonType.qml delete mode 100644 client/ui/qml/Controls/SvgImageType.qml delete mode 100644 client/ui/qml/Controls/TextAreaType.qml delete mode 100644 client/ui/qml/Controls/TextFieldType.qml delete mode 100644 client/ui/qml/Controls/UrlButtonType.qml delete mode 100644 client/ui/qml/Controls/VisibleBehavior.qml delete mode 100644 client/ui/qml/Pages/ClientInfo/PageClientInfoBase.qml delete mode 100644 client/ui/qml/Pages/ClientInfo/PageClientInfoOpenVPN.qml delete mode 100644 client/ui/qml/Pages/ClientInfo/PageClientInfoWireGuard.qml delete mode 100644 client/ui/qml/Pages/InstallSettings/InstallSettingsBase.qml delete mode 100644 client/ui/qml/Pages/InstallSettings/SelectContainer.qml delete mode 100644 client/ui/qml/Pages/PageAbout.qml delete mode 100644 client/ui/qml/Pages/PageAdvancedServerSettings.qml delete mode 100644 client/ui/qml/Pages/PageAppSetting.qml delete mode 100644 client/ui/qml/Pages/PageBase.qml delete mode 100644 client/ui/qml/Pages/PageClientManagement.qml delete mode 100644 client/ui/qml/Pages/PageGeneralSettings.qml delete mode 100644 client/ui/qml/Pages/PageNetworkSetting.qml delete mode 100644 client/ui/qml/Pages/PageNewServer.qml delete mode 100644 client/ui/qml/Pages/PageNewServerProtocols.qml delete mode 100644 client/ui/qml/Pages/PageQrDecoderIos.qml delete mode 100644 client/ui/qml/Pages/PageServerConfiguringProgress.qml delete mode 100644 client/ui/qml/Pages/PageServerContainers.qml delete mode 100644 client/ui/qml/Pages/PageServerList.qml delete mode 100644 client/ui/qml/Pages/PageServerSettings.qml delete mode 100644 client/ui/qml/Pages/PageSetupWizard.qml delete mode 100644 client/ui/qml/Pages/PageSetupWizardHighLevel.qml delete mode 100644 client/ui/qml/Pages/PageSetupWizardLowLevel.qml delete mode 100644 client/ui/qml/Pages/PageSetupWizardMediumLevel.qml delete mode 100644 client/ui/qml/Pages/PageSetupWizardVPNMode.qml delete mode 100644 client/ui/qml/Pages/PageShareConnection.qml delete mode 100644 client/ui/qml/Pages/PageSites.qml delete mode 100644 client/ui/qml/Pages/PageStart.qml delete mode 100644 client/ui/qml/Pages/PageVPN.qml delete mode 100644 client/ui/qml/Pages/PageViewConfig.qml delete mode 100644 client/ui/qml/Pages/Protocols/PageProtoCloak.qml delete mode 100644 client/ui/qml/Pages/Protocols/PageProtoOpenVPN.qml delete mode 100644 client/ui/qml/Pages/Protocols/PageProtoSftp.qml delete mode 100644 client/ui/qml/Pages/Protocols/PageProtoShadowSocks.qml delete mode 100644 client/ui/qml/Pages/Protocols/PageProtoTorWebSite.qml delete mode 100644 client/ui/qml/Pages/Protocols/PageProtoWireGuard.qml delete mode 100644 client/ui/qml/Pages/Protocols/PageProtocolBase.qml delete mode 100644 client/ui/qml/Pages/Share/PageShareProtoAmnezia.qml delete mode 100644 client/ui/qml/Pages/Share/PageShareProtoCloak.qml delete mode 100644 client/ui/qml/Pages/Share/PageShareProtoIkev2.qml delete mode 100644 client/ui/qml/Pages/Share/PageShareProtoOpenVPN.qml delete mode 100644 client/ui/qml/Pages/Share/PageShareProtoSftp.qml delete mode 100644 client/ui/qml/Pages/Share/PageShareProtoShadowSocks.qml delete mode 100644 client/ui/qml/Pages/Share/PageShareProtoTorWebSite.qml delete mode 100644 client/ui/qml/Pages/Share/PageShareProtoWireGuard.qml delete mode 100644 client/ui/qml/Pages/Share/PageShareProtocolBase.qml delete mode 100644 client/ui/qml/main.qml delete mode 100644 client/ui/uilogic.cpp delete mode 100644 client/ui/uilogic.h diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 47264fe6..ca5161cf 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -95,7 +95,6 @@ set(HEADERS ${HEADERS} ${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.h ${CMAKE_CURRENT_LIST_DIR}/ui/pages.h ${CMAKE_CURRENT_LIST_DIR}/ui/property_helper.h - ${CMAKE_CURRENT_LIST_DIR}/ui/uilogic.h ${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.h ${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.h ${CMAKE_CURRENT_BINARY_DIR}/version.h @@ -132,7 +131,6 @@ set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/core/servercontroller.cpp ${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.cpp ${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.cpp - ${CMAKE_CURRENT_LIST_DIR}/ui/uilogic.cpp ${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.cpp ${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.cpp ${CMAKE_CURRENT_LIST_DIR}/core/sshclient.cpp diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index c4586748..d14917f0 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -156,7 +156,7 @@ void AmneziaApplication::init() // Android TextField clipboard workaround // https://bugreports.qt.io/browse/QTBUG-113461 #ifdef Q_OS_ANDROID - QObject::connect(qApp, &QApplication::applicationStateChanged, [](Qt::ApplicationState state) { + QObject::connect(qApp, &QGuiApplication::applicationStateChanged, [](Qt::ApplicationState state) { if (state == Qt::ApplicationActive) { if (qApp->clipboard()->mimeData()->formats().contains("text/html")) { QTextDocument doc; diff --git a/client/amnezia_application.h b/client/amnezia_application.h index e18fb70c..40ea81b4 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -41,7 +41,7 @@ #define amnApp (static_cast(QCoreApplication::instance())) #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) - #define AMNEZIA_BASE_CLASS QGuiApp + #define AMNEZIA_BASE_CLASS QGuiApplication #else #define AMNEZIA_BASE_CLASS SingleApplication #define QAPPLICATION_CLASS QApplication diff --git a/client/configurators/ikev2_configurator.cpp b/client/configurators/ikev2_configurator.cpp index 7ed83da1..4ca0e5da 100644 --- a/client/configurators/ikev2_configurator.cpp +++ b/client/configurators/ikev2_configurator.cpp @@ -1,28 +1,26 @@ #include "ikev2_configurator.h" -#include + +#include +#include #include #include #include -#include #include -#include #include #include "containers/containers_defs.h" -#include "core/server_defs.h" #include "core/scripts_registry.h" -#include "utilities.h" +#include "core/server_defs.h" #include "core/servercontroller.h" +#include "utilities.h" - -Ikev2Configurator::Ikev2Configurator(std::shared_ptr settings, QObject *parent): - ConfiguratorBase(settings, parent) +Ikev2Configurator::Ikev2Configurator(std::shared_ptr settings, QObject *parent) + : ConfiguratorBase(settings, parent) { - } Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const ServerCredentials &credentials, - DockerContainer container, ErrorCode *errorCode) + DockerContainer container, ErrorCode *errorCode) { Ikev2Configurator::ConnectionData connData; connData.host = credentials.hostName; @@ -32,26 +30,27 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se QString certFileName = "/opt/amnezia/ikev2/clients/" + connData.clientId + ".p12"; - QString scriptCreateCert = QString("certutil -z <(head -c 1024 /dev/urandom) "\ - "-S -c \"IKEv2 VPN CA\" -n \"%1\" "\ - "-s \"O=IKEv2 VPN,CN=%1\" "\ - "-k rsa -g 3072 -v 120 "\ - "-d sql:/etc/ipsec.d -t \",,\" "\ - "--keyUsage digitalSignature,keyEncipherment "\ - "--extKeyUsage serverAuth,clientAuth -8 \"%1\"") - .arg(connData.clientId); + QString scriptCreateCert = QString("certutil -z <(head -c 1024 /dev/urandom) " + "-S -c \"IKEv2 VPN CA\" -n \"%1\" " + "-s \"O=IKEv2 VPN,CN=%1\" " + "-k rsa -g 3072 -v 120 " + "-d sql:/etc/ipsec.d -t \",,\" " + "--keyUsage digitalSignature,keyEncipherment " + "--extKeyUsage serverAuth,clientAuth -8 \"%1\"") + .arg(connData.clientId); ServerController serverController(m_settings); ErrorCode e = serverController.runContainerScript(credentials, container, scriptCreateCert); QString scriptExportCert = QString("pk12util -W \"%1\" -d sql:/etc/ipsec.d -n \"%2\" -o \"%3\"") - .arg(connData.password) - .arg(connData.clientId) - .arg(certFileName); + .arg(connData.password) + .arg(connData.clientId) + .arg(certFileName); e = serverController.runContainerScript(credentials, container, scriptExportCert); connData.clientCert = serverController.getTextFileFromContainer(container, credentials, certFileName, &e); - connData.caCert = serverController.getTextFileFromContainer(container, credentials, "/etc/ipsec.d/ca_cert_base64.p12", &e); + connData.caCert = + serverController.getTextFileFromContainer(container, credentials, "/etc/ipsec.d/ca_cert_base64.p12", &e); qDebug() << "Ikev2Configurator::ConnectionData client cert size:" << connData.clientCert.size(); qDebug() << "Ikev2Configurator::ConnectionData ca cert size:" << connData.caCert.size(); @@ -59,8 +58,8 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se return connData; } -QString Ikev2Configurator::genIkev2Config(const ServerCredentials &credentials, - DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode) +QString Ikev2Configurator::genIkev2Config(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode *errorCode) { Q_UNUSED(containerConfig) @@ -120,4 +119,3 @@ QString Ikev2Configurator::genStrongSwanConfig(const ConnectionData &connData) return config; } - diff --git a/client/configurators/openvpn_configurator.cpp b/client/configurators/openvpn_configurator.cpp index 3bc6676a..bfde4a91 100644 --- a/client/configurators/openvpn_configurator.cpp +++ b/client/configurators/openvpn_configurator.cpp @@ -1,82 +1,89 @@ #include "openvpn_configurator.h" -#include + +#include +#include +#include #include #include #include -#include #include -#include -#include #include "containers/containers_defs.h" +#include "core/scripts_registry.h" #include "core/server_defs.h" #include "core/servercontroller.h" -#include "core/scripts_registry.h" -#include "utilities.h" #include "settings.h" +#include "utilities.h" +#include #include #include -#include -OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr settings, QObject *parent): - ConfiguratorBase(settings, parent) +OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr settings, QObject *parent) + : ConfiguratorBase(settings, parent) { - } OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(const ServerCredentials &credentials, - DockerContainer container, ErrorCode *errorCode) + DockerContainer container, + ErrorCode *errorCode) { OpenVpnConfigurator::ConnectionData connData = OpenVpnConfigurator::createCertRequest(); connData.host = credentials.hostName; if (connData.privKey.isEmpty() || connData.request.isEmpty()) { - if (errorCode) *errorCode = ErrorCode::OpenSslFailed; + if (errorCode) + *errorCode = ErrorCode::OpenSslFailed; return connData; } - QString reqFileName = QString("%1/%2.req"). - arg(amnezia::protocols::openvpn::clientsDirPath). - arg(connData.clientId); + QString reqFileName = QString("%1/%2.req").arg(amnezia::protocols::openvpn::clientsDirPath).arg(connData.clientId); ServerController serverController(m_settings); ErrorCode e = serverController.uploadTextFileToContainer(container, credentials, connData.request, reqFileName); if (e) { - if (errorCode) *errorCode = e; + if (errorCode) + *errorCode = e; return connData; } e = signCert(container, credentials, connData.clientId); if (e) { - if (errorCode) *errorCode = e; + if (errorCode) + *errorCode = e; return connData; } - connData.caCert = serverController.getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::caCertPath, &e); - connData.clientCert = serverController.getTextFileFromContainer(container, credentials, - QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath).arg(connData.clientId), &e); + connData.caCert = serverController.getTextFileFromContainer(container, credentials, + amnezia::protocols::openvpn::caCertPath, &e); + connData.clientCert = serverController.getTextFileFromContainer( + container, credentials, + QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath).arg(connData.clientId), &e); if (e) { - if (errorCode) *errorCode = e; + if (errorCode) + *errorCode = e; return connData; } - connData.taKey = serverController.getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::taKeyPath, &e); + connData.taKey = serverController.getTextFileFromContainer(container, credentials, + amnezia::protocols::openvpn::taKeyPath, &e); if (connData.caCert.isEmpty() || connData.clientCert.isEmpty() || connData.taKey.isEmpty()) { - if (errorCode) *errorCode = ErrorCode::SshSftpFailureError; + if (errorCode) + *errorCode = ErrorCode::SshSftpFailureError; } return connData; } -QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentials, - DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode) +QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode *errorCode) { ServerController serverController(m_settings); - QString config = serverController.replaceVars(amnezia::scriptData(ProtocolScriptType::openvpn_template, container), - serverController.genVarsForScript(credentials, container, containerConfig)); + QString config = + serverController.replaceVars(amnezia::scriptData(ProtocolScriptType::openvpn_template, container), + serverController.genVarsForScript(credentials, container, containerConfig)); ConnectionData connData = prepareOpenVpnConfig(credentials, container, errorCode); if (errorCode && *errorCode) { @@ -89,8 +96,7 @@ QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentia if (config.contains("$OPENVPN_TA_KEY")) { config.replace("$OPENVPN_TA_KEY", connData.taKey); - } - else { + } else { config.replace("", ""); config.replace("", ""); } @@ -133,12 +139,11 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig) config.replace("block-outside-dns", ""); #endif -#if (defined (MZ_MACOS) || defined(MZ_LINUX)) - QString dnsConf = QString( - "\nscript-security 2\n" - "up %1/update-resolv-conf.sh\n" - "down %1/update-resolv-conf.sh\n"). - arg(qApp->applicationDirPath()); +#if (defined(MZ_MACOS) || defined(MZ_LINUX)) + QString dnsConf = QString("\nscript-security 2\n" + "up %1/update-resolv-conf.sh\n" + "down %1/update-resolv-conf.sh\n") + .arg(qApp->applicationDirPath()); config.append(dnsConf); #endif @@ -168,23 +173,23 @@ QString OpenVpnConfigurator::processConfigWithExportSettings(QString jsonConfig) return QJsonDocument(json).toJson(); } -ErrorCode OpenVpnConfigurator::signCert(DockerContainer container, - const ServerCredentials &credentials, QString clientId) +ErrorCode OpenVpnConfigurator::signCert(DockerContainer container, const ServerCredentials &credentials, QString clientId) { QString script_import = QString("sudo docker exec -i %1 bash -c \"cd /opt/amnezia/openvpn && " - "easyrsa import-req %2/%3.req %3\"") - .arg(ContainerProps::containerToString(container)) - .arg(amnezia::protocols::openvpn::clientsDirPath) - .arg(clientId); + "easyrsa import-req %2/%3.req %3\"") + .arg(ContainerProps::containerToString(container)) + .arg(amnezia::protocols::openvpn::clientsDirPath) + .arg(clientId); QString script_sign = QString("sudo docker exec -i %1 bash -c \"export EASYRSA_BATCH=1; cd /opt/amnezia/openvpn && " - "easyrsa sign-req client %2\"") - .arg(ContainerProps::containerToString(container)) - .arg(clientId); + "easyrsa sign-req client %2\"") + .arg(ContainerProps::containerToString(container)) + .arg(clientId); ServerController serverController(m_settings); - QStringList scriptList {script_import, script_sign}; - QString script = serverController.replaceVars(scriptList.join("\n"), serverController.genVarsForScript(credentials, container)); + QStringList scriptList { script_import, script_sign }; + QString script = serverController.replaceVars(scriptList.join("\n"), + serverController.genVarsForScript(credentials, container)); return serverController.runScript(credentials, script); } @@ -194,18 +199,17 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest() ConnectionData connData; connData.clientId = Utils::getRandomString(32); - int ret = 0; - int nVersion = 1; + int ret = 0; + int nVersion = 1; QByteArray clientIdUtf8 = connData.clientId.toUtf8(); - EVP_PKEY * pKey = EVP_PKEY_new(); + EVP_PKEY *pKey = EVP_PKEY_new(); q_check_ptr(pKey); - RSA * rsa = RSA_generate_key(2048, RSA_F4, nullptr, nullptr); + RSA *rsa = RSA_generate_key(2048, RSA_F4, nullptr, nullptr); q_check_ptr(rsa); EVP_PKEY_assign_RSA(pKey, rsa); - // 2. set version of x509 req X509_REQ *x509_req = X509_REQ_new(); ret = X509_REQ_set_version(x509_req, nVersion); @@ -219,16 +223,14 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest() // 3. set subject of x509 req X509_NAME *x509_name = X509_REQ_get_subject_name(x509_req); - X509_NAME_add_entry_by_txt(x509_name, "C", MBSTRING_ASC, - (unsigned char *)"ORG", -1, -1, 0); - X509_NAME_add_entry_by_txt(x509_name, "O", MBSTRING_ASC, - (unsigned char *)"", -1, -1, 0); + X509_NAME_add_entry_by_txt(x509_name, "C", MBSTRING_ASC, (unsigned char *)"ORG", -1, -1, 0); + X509_NAME_add_entry_by_txt(x509_name, "O", MBSTRING_ASC, (unsigned char *)"", -1, -1, 0); X509_NAME_add_entry_by_txt(x509_name, "CN", MBSTRING_ASC, reinterpret_cast(clientIdUtf8.data()), clientIdUtf8.size(), -1, 0); // 4. set public key of x509 req ret = X509_REQ_set_pubkey(x509_req, pKey); - if (ret != 1){ + if (ret != 1) { qWarning() << "Could not set pubkey!"; X509_REQ_free(x509_req); EVP_PKEY_free(pKey); @@ -236,8 +238,8 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest() } // 5. set sign key of x509 req - ret = X509_REQ_sign(x509_req, pKey, EVP_sha256()); // return x509_req->signature->length - if (ret <= 0){ + ret = X509_REQ_sign(x509_req, pKey, EVP_sha256()); // return x509_req->signature->length + if (ret <= 0) { qWarning() << "Could not sign request!"; X509_REQ_free(x509_req); EVP_PKEY_free(pKey); @@ -245,10 +247,9 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest() } // save private key - BIO * bp_private = BIO_new(BIO_s_mem()); + BIO *bp_private = BIO_new(BIO_s_mem()); q_check_ptr(bp_private); - if (PEM_write_bio_PrivateKey(bp_private, pKey, nullptr, nullptr, 0, nullptr, nullptr) != 1) - { + if (PEM_write_bio_PrivateKey(bp_private, pKey, nullptr, nullptr, 0, nullptr, nullptr) != 1) { qFatal("PEM_write_bio_PrivateKey"); EVP_PKEY_free(pKey); BIO_free_all(bp_private); @@ -256,7 +257,7 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest() return connData; } - const char * buffer = nullptr; + const char *buffer = nullptr; size_t size = BIO_get_mem_data(bp_private, &buffer); q_check_ptr(buffer); connData.privKey = QByteArray(buffer, size); @@ -270,7 +271,7 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest() BIO_free_all(bp_private); // save req - BIO * bio_req = BIO_new(BIO_s_mem()); + BIO *bio_req = BIO_new(BIO_s_mem()); PEM_write_bio_X509_REQ(bio_req, x509_req); BUF_MEM *bio_buf; @@ -278,7 +279,6 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest() connData.request = QByteArray(bio_buf->data, bio_buf->length); BIO_free(bio_req); - EVP_PKEY_free(pKey); // this will also free the rsa key return connData; diff --git a/client/configurators/ssh_configurator.cpp b/client/configurators/ssh_configurator.cpp index d94e4468..42e7eb47 100644 --- a/client/configurators/ssh_configurator.cpp +++ b/client/configurators/ssh_configurator.cpp @@ -1,24 +1,25 @@ #include "ssh_configurator.h" -#include + +#include +#include #include #include #include -#include #include #include -#include -#include -#include #include +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) + #include +#else + #include +#endif #include "core/server_defs.h" #include "utilities.h" - -SshConfigurator::SshConfigurator(std::shared_ptr settings, QObject *parent): - ConfiguratorBase(settings, parent) +SshConfigurator::SshConfigurator(std::shared_ptr settings, QObject *parent) + : ConfiguratorBase(settings, parent) { - } QString SshConfigurator::convertOpenSShKey(const QString &key) @@ -28,23 +29,30 @@ QString SshConfigurator::convertOpenSShKey(const QString &key) p.setProcessChannelMode(QProcess::MergedChannels); QTemporaryFile tmp; -#ifdef QT_DEBUG + #ifdef QT_DEBUG tmp.setAutoRemove(false); -#endif + #endif tmp.open(); tmp.write(key.toUtf8()); tmp.close(); // ssh-keygen -p -P "" -N "" -m pem -f id_ssh -#ifdef Q_OS_WIN + #ifdef Q_OS_WIN p.setProcessEnvironment(prepareEnv()); p.setProgram("cmd.exe"); p.setNativeArguments(QString("/C \"ssh-keygen.exe -p -P \"\" -N \"\" -m pem -f \"%1\"\"").arg(tmp.fileName())); -#else + #else p.setProgram("ssh-keygen"); - p.setArguments(QStringList() << "-p" << "-P" << "" << "-N" << "" << "-m" << "pem" << "-f" << tmp.fileName()); -#endif + p.setArguments(QStringList() << "-p" + << "-P" + << "" + << "-N" + << "" + << "-m" + << "pem" + << "-f" << tmp.fileName()); + #endif p.start(); p.waitForFinished(); @@ -65,22 +73,21 @@ void SshConfigurator::openSshTerminal(const ServerCredentials &credentials) QProcess *p = new QProcess(); p->setProcessChannelMode(QProcess::SeparateChannels); -#ifdef Q_OS_WIN + #ifdef Q_OS_WIN p->setProcessEnvironment(prepareEnv()); p->setProgram(qApp->applicationDirPath() + "\\cygwin\\putty.exe"); if (credentials.secretData.contains("PRIVATE KEY")) { // todo: connect by key -// p->setNativeArguments(QString("%1@%2") -// .arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData)); + // p->setNativeArguments(QString("%1@%2") + // .arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData)); + } else { + p->setNativeArguments( + QString("%1@%2 -pw %3").arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData)); } - else { - p->setNativeArguments(QString("%1@%2 -pw %3") - .arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData)); - } -#else + #else p->setProgram("/bin/bash"); -#endif + #endif p->startDetached(); #endif @@ -95,11 +102,11 @@ QProcessEnvironment SshConfigurator::prepareEnv() pathEnvVar.clear(); pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\cygwin;"); pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\openvpn;"); -#else +#elif defined(Q_OS_MACX) pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/Contents/MacOS"); #endif env.insert("PATH", pathEnvVar); - //qDebug().noquote() << "ENV PATH" << pathEnvVar; + // qDebug().noquote() << "ENV PATH" << pathEnvVar; return env; } diff --git a/client/configurators/wireguard_configurator.cpp b/client/configurators/wireguard_configurator.cpp index 54ee320c..14059977 100644 --- a/client/configurators/wireguard_configurator.cpp +++ b/client/configurators/wireguard_configurator.cpp @@ -1,30 +1,27 @@ #include "wireguard_configurator.h" -#include + +#include +#include #include #include #include -#include #include -#include - +#include #include #include #include -#include - #include "containers/containers_defs.h" -#include "core/server_defs.h" #include "core/scripts_registry.h" -#include "utilities.h" +#include "core/server_defs.h" #include "core/servercontroller.h" #include "settings.h" +#include "utilities.h" -WireguardConfigurator::WireguardConfigurator(std::shared_ptr settings, QObject *parent): - ConfiguratorBase(settings, parent) +WireguardConfigurator::WireguardConfigurator(std::shared_ptr settings, QObject *parent) + : ConfiguratorBase(settings, parent) { - } WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys() @@ -36,37 +33,40 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys() unsigned char buff[EDDSA_KEY_LENGTH]; int ret = RAND_priv_bytes(buff, EDDSA_KEY_LENGTH); - if (ret <=0) return connData; + if (ret <= 0) + return connData; - EVP_PKEY * pKey = EVP_PKEY_new(); + EVP_PKEY *pKey = EVP_PKEY_new(); q_check_ptr(pKey); pKey = EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, NULL, &buff[0], EDDSA_KEY_LENGTH); - size_t keySize = EDDSA_KEY_LENGTH; // save private key unsigned char priv[EDDSA_KEY_LENGTH]; EVP_PKEY_get_raw_private_key(pKey, priv, &keySize); - connData.clientPrivKey = QByteArray::fromRawData((char*)priv, keySize).toBase64(); + connData.clientPrivKey = QByteArray::fromRawData((char *)priv, keySize).toBase64(); // save public key unsigned char pub[EDDSA_KEY_LENGTH]; EVP_PKEY_get_raw_public_key(pKey, pub, &keySize); - connData.clientPubKey = QByteArray::fromRawData((char*)pub, keySize).toBase64(); + connData.clientPubKey = QByteArray::fromRawData((char *)pub, keySize).toBase64(); return connData; } WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardConfig(const ServerCredentials &credentials, - DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode) + DockerContainer container, + const QJsonObject &containerConfig, + ErrorCode *errorCode) { WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys(); connData.host = credentials.hostName; connData.port = containerConfig.value(config_key::port).toString(protocols::wireguard::defaultPort); if (connData.clientPrivKey.isEmpty() || connData.clientPubKey.isEmpty()) { - if (errorCode) *errorCode = ErrorCode::InternalError; + if (errorCode) + *errorCode = ErrorCode::InternalError; return connData; } @@ -96,22 +96,24 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon // Calc next IP address if (ips.isEmpty()) { nextIpNumber = "2"; - } - else { + } else { int next = ips.last().split(".").last().toInt() + 1; if (next > 254) { - if (errorCode) *errorCode = ErrorCode::AddressPoolError; + if (errorCode) + *errorCode = ErrorCode::AddressPoolError; return connData; } nextIpNumber = QString::number(next); } } - QString subnetIp = containerConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress); + QString subnetIp = + containerConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress); { QStringList l = subnetIp.split(".", Qt::SkipEmptyParts); if (l.isEmpty()) { - if (errorCode) *errorCode = ErrorCode::AddressPoolError; + if (errorCode) + *errorCode = ErrorCode::AddressPoolError; return connData; } l.removeLast(); @@ -121,52 +123,60 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon } // Get keys - connData.serverPubKey = serverController.getTextFileFromContainer(container, credentials, amnezia::protocols::wireguard::serverPublicKeyPath, &e); + connData.serverPubKey = serverController.getTextFileFromContainer( + container, credentials, amnezia::protocols::wireguard::serverPublicKeyPath, &e); connData.serverPubKey.replace("\n", ""); if (e) { - if (errorCode) *errorCode = e; + if (errorCode) + *errorCode = e; return connData; } - connData.pskKey = serverController.getTextFileFromContainer(container, credentials, amnezia::protocols::wireguard::serverPskKeyPath, &e); + connData.pskKey = serverController.getTextFileFromContainer(container, credentials, + amnezia::protocols::wireguard::serverPskKeyPath, &e); connData.pskKey.replace("\n", ""); if (e) { - if (errorCode) *errorCode = e; + if (errorCode) + *errorCode = e; return connData; } // Add client to config - QString configPart = QString( - "[Peer]\n" - "PublicKey = %1\n" - "PresharedKey = %2\n" - "AllowedIPs = %3/32\n\n"). - arg(connData.clientPubKey). - arg(connData.pskKey). - arg(connData.clientIP); + QString configPart = QString("[Peer]\n" + "PublicKey = %1\n" + "PresharedKey = %2\n" + "AllowedIPs = %3/32\n\n") + .arg(connData.clientPubKey) + .arg(connData.pskKey) + .arg(connData.clientIP); e = serverController.uploadTextFileToContainer(container, credentials, configPart, - protocols::wireguard::serverConfigPath, libssh::SftpOverwriteMode::SftpAppendToExisting); + protocols::wireguard::serverConfigPath, + libssh::SftpOverwriteMode::SftpAppendToExisting); if (e) { - if (errorCode) *errorCode = e; + if (errorCode) + *errorCode = e; return connData; } - e = serverController.runScript(credentials, - serverController.replaceVars("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip /opt/amnezia/wireguard/wg0.conf)'", - serverController.genVarsForScript(credentials, container))); + e = serverController.runScript( + credentials, + serverController.replaceVars("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick " + "strip /opt/amnezia/wireguard/wg0.conf)'", + serverController.genVarsForScript(credentials, container))); return connData; } -QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &credentials, - DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode) +QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode *errorCode) { ServerController serverController(m_settings); - QString config = serverController.replaceVars(amnezia::scriptData(ProtocolScriptType::wireguard_template, container), - serverController.genVarsForScript(credentials, container, containerConfig)); + QString config = + serverController.replaceVars(amnezia::scriptData(ProtocolScriptType::wireguard_template, container), + serverController.genVarsForScript(credentials, container, containerConfig)); ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode); if (errorCode && *errorCode) { diff --git a/client/fileUtilites.cpp b/client/fileUtilites.cpp index c1b60966..b3c65216 100644 --- a/client/fileUtilites.cpp +++ b/client/fileUtilites.cpp @@ -1,7 +1,9 @@ #include "fileUtilites.h" -#include "platforms/ios/MobileUtils.h" #include +#include +#include +#include #include #ifdef Q_OS_ANDROID diff --git a/client/platforms/android/androidutils.cpp b/client/platforms/android/androidutils.cpp index 5e9f094c..7cc39824 100644 --- a/client/platforms/android/androidutils.cpp +++ b/client/platforms/android/androidutils.cpp @@ -4,23 +4,25 @@ #include "androidutils.h" -#include +#include #include #include #include #include #include -#include #include +#include #include "jni.h" -namespace { - AndroidUtils* s_instance = nullptr; -} // namespace +namespace +{ + AndroidUtils *s_instance = nullptr; +} // namespace // static -QString AndroidUtils::GetDeviceName() { +QString AndroidUtils::GetDeviceName() +{ QJniEnvironment env; jclass BUILD = env->FindClass("android/os/Build"); jfieldID model = env->GetStaticFieldID(BUILD, "MODEL", "Ljava/lang/String;"); @@ -30,7 +32,7 @@ QString AndroidUtils::GetDeviceName() { return QString("Android Device"); } - const char* buffer = env->GetStringUTFChars(value, nullptr); + const char *buffer = env->GetStringUTFChars(value, nullptr); if (!buffer) { return QString("Android Device"); } @@ -42,7 +44,8 @@ QString AndroidUtils::GetDeviceName() { }; // static -AndroidUtils* AndroidUtils::instance() { +AndroidUtils *AndroidUtils::instance() +{ if (!s_instance) { Q_ASSERT(qApp); s_instance = new AndroidUtils(qApp); @@ -51,19 +54,22 @@ AndroidUtils* AndroidUtils::instance() { return s_instance; } -AndroidUtils::AndroidUtils(QObject* parent) : QObject(parent) { +AndroidUtils::AndroidUtils(QObject *parent) : QObject(parent) +{ Q_ASSERT(!s_instance); s_instance = this; } -AndroidUtils::~AndroidUtils() { +AndroidUtils::~AndroidUtils() +{ Q_ASSERT(s_instance == this); s_instance = nullptr; } // static -void AndroidUtils::dispatchToMainThread(std::function callback) { - QTimer* timer = new QTimer(); +void AndroidUtils::dispatchToMainThread(std::function callback) +{ + QTimer *timer = new QTimer(); timer->moveToThread(qApp->thread()); timer->setSingleShot(true); QObject::connect(timer, &QTimer::timeout, [=]() { @@ -74,8 +80,9 @@ void AndroidUtils::dispatchToMainThread(std::function callback) { } // static -QByteArray AndroidUtils::getQByteArrayFromJString(JNIEnv* env, jstring data) { - const char* buffer = env->GetStringUTFChars(data, nullptr); +QByteArray AndroidUtils::getQByteArrayFromJString(JNIEnv *env, jstring data) +{ + const char *buffer = env->GetStringUTFChars(data, nullptr); if (!buffer) { qDebug() << "getQByteArrayFromJString - failed to parse data."; return QByteArray(); @@ -87,8 +94,9 @@ QByteArray AndroidUtils::getQByteArrayFromJString(JNIEnv* env, jstring data) { } // static -QString AndroidUtils::getQStringFromJString(JNIEnv* env, jstring data) { - const char* buffer = env->GetStringUTFChars(data, nullptr); +QString AndroidUtils::getQStringFromJString(JNIEnv *env, jstring data) +{ + const char *buffer = env->GetStringUTFChars(data, nullptr); if (!buffer) { qDebug() << "getQStringFromJString - failed to parse data."; return QString(); @@ -100,15 +108,14 @@ QString AndroidUtils::getQStringFromJString(JNIEnv* env, jstring data) { } // static -QJsonObject AndroidUtils::getQJsonObjectFromJString(JNIEnv* env, jstring data) { +QJsonObject AndroidUtils::getQJsonObjectFromJString(JNIEnv *env, jstring data) +{ QByteArray raw(getQByteArrayFromJString(env, data)); QJsonParseError jsonError; QJsonDocument json = QJsonDocument::fromJson(raw, &jsonError); if (QJsonParseError::NoError != jsonError.error) { - qDebug() << "getQJsonObjectFromJstring - error parsing json. Code: " - << jsonError.error << "Offset: " << jsonError.offset - << "Message: " << jsonError.errorString() - << "Data: " << raw; + qDebug() << "getQJsonObjectFromJstring - error parsing json. Code: " << jsonError.error + << "Offset: " << jsonError.offset << "Message: " << jsonError.errorString() << "Data: " << raw; return QJsonObject(); } @@ -120,11 +127,13 @@ QJsonObject AndroidUtils::getQJsonObjectFromJString(JNIEnv* env, jstring data) { return json.object(); } -QJniObject AndroidUtils::getActivity() { +QJniObject AndroidUtils::getActivity() +{ return QNativeInterface::QAndroidApplication::context(); } -int AndroidUtils::GetSDKVersion() { +int AndroidUtils::GetSDKVersion() +{ QJniEnvironment env; jclass versionClass = env->FindClass("android/os/Build$VERSION"); jfieldID sdkIntFieldID = env->GetStaticFieldID(versionClass, "SDK_INT", "I"); @@ -133,15 +142,14 @@ int AndroidUtils::GetSDKVersion() { return sdk; } -QString AndroidUtils::GetManufacturer() { +QString AndroidUtils::GetManufacturer() +{ QJniEnvironment env; jclass buildClass = env->FindClass("android/os/Build"); - jfieldID manuFacturerField = - env->GetStaticFieldID(buildClass, "MANUFACTURER", "Ljava/lang/String;"); - jstring value = - (jstring)env->GetStaticObjectField(buildClass, manuFacturerField); + jfieldID manuFacturerField = env->GetStaticFieldID(buildClass, "MANUFACTURER", "Ljava/lang/String;"); + jstring value = (jstring)env->GetStaticObjectField(buildClass, manuFacturerField); - const char* buffer = env->GetStringUTFChars(value, nullptr); + const char *buffer = env->GetStringUTFChars(value, nullptr); if (!buffer) { qDebug() << "Failed to fetch MANUFACTURER"; @@ -154,21 +162,22 @@ QString AndroidUtils::GetManufacturer() { return res; } -void AndroidUtils::runOnAndroidThreadSync(const std::function runnable) { - QNativeInterface::QAndroidApplication::runOnAndroidMainThread(runnable) - .waitForFinished(); +void AndroidUtils::runOnAndroidThreadSync(const std::function runnable) +{ + QNativeInterface::QAndroidApplication::runOnAndroidMainThread(runnable).waitForFinished(); } -void AndroidUtils::runOnAndroidThreadAsync(const std::function runnable) { +void AndroidUtils::runOnAndroidThreadAsync(const std::function runnable) +{ QNativeInterface::QAndroidApplication::runOnAndroidMainThread(runnable); } // Static // Creates a copy of the passed QByteArray in the JVM and passes back a ref -jbyteArray AndroidUtils::tojByteArray(const QByteArray& data) { +jbyteArray AndroidUtils::tojByteArray(const QByteArray &data) +{ QJniEnvironment env; jbyteArray out = env->NewByteArray(data.size()); - env->SetByteArrayRegion(out, 0, data.size(), - reinterpret_cast(data.constData())); + env->SetByteArrayRegion(out, 0, data.size(), reinterpret_cast(data.constData())); return out; } diff --git a/client/platforms/android/androidvpnactivity.cpp b/client/platforms/android/androidvpnactivity.cpp index 2076280d..e81e694b 100644 --- a/client/platforms/android/androidvpnactivity.cpp +++ b/client/platforms/android/androidvpnactivity.cpp @@ -4,7 +4,6 @@ #include "androidvpnactivity.h" -#include #include #include #include @@ -13,19 +12,21 @@ #include "androidutils.h" #include "jni.h" -namespace { - AndroidVPNActivity* s_instance = nullptr; +namespace +{ + AndroidVPNActivity *s_instance = nullptr; constexpr auto CLASSNAME = "org.amnezia.vpn.qt.VPNActivity"; } -AndroidVPNActivity::AndroidVPNActivity() { +AndroidVPNActivity::AndroidVPNActivity() +{ AndroidUtils::runOnAndroidThreadAsync([]() { - JNINativeMethod methods[]{ - {"handleBackButton", "()Z", reinterpret_cast(handleBackButton)}, - {"onServiceMessage", "(ILjava/lang/String;)V", reinterpret_cast(onServiceMessage)}, - {"qtOnServiceConnected", "()V", reinterpret_cast(onServiceConnected)}, - {"qtOnServiceDisconnected", "()V", reinterpret_cast(onServiceDisconnected)}, - {"onActivityMessage", "(ILjava/lang/String;)V", reinterpret_cast(onAndroidVpnActivityMessage)} + JNINativeMethod methods[] { + { "handleBackButton", "()Z", reinterpret_cast(handleBackButton) }, + { "onServiceMessage", "(ILjava/lang/String;)V", reinterpret_cast(onServiceMessage) }, + { "qtOnServiceConnected", "()V", reinterpret_cast(onServiceConnected) }, + { "qtOnServiceDisconnected", "()V", reinterpret_cast(onServiceDisconnected) }, + { "onActivityMessage", "(ILjava/lang/String;)V", reinterpret_cast(onAndroidVpnActivityMessage) } }; QJniObject javaClass(CLASSNAME); @@ -36,19 +37,22 @@ AndroidVPNActivity::AndroidVPNActivity() { }); } -void AndroidVPNActivity::maybeInit() { +void AndroidVPNActivity::maybeInit() +{ if (s_instance == nullptr) { s_instance = new AndroidVPNActivity(); } } // static -bool AndroidVPNActivity::handleBackButton(JNIEnv* env, jobject thiz) { +bool AndroidVPNActivity::handleBackButton(JNIEnv *env, jobject thiz) +{ Q_UNUSED(env); Q_UNUSED(thiz); } -void AndroidVPNActivity::connectService() { +void AndroidVPNActivity::connectService() +{ QJniObject::callStaticMethod(CLASSNAME, "connectService", "()V"); } @@ -57,16 +61,16 @@ void AndroidVPNActivity::startQrCodeReader() QJniObject::callStaticMethod(CLASSNAME, "startQrCodeReader", "()V"); } -void AndroidVPNActivity::saveFileAs(QString fileContent, QString suggestedFilename) { - QJniObject::callStaticMethod( - CLASSNAME, - "saveFileAs", "(Ljava/lang/String;Ljava/lang/String;)V", - QJniObject::fromString(fileContent).object(), - QJniObject::fromString(suggestedFilename).object()); +void AndroidVPNActivity::saveFileAs(QString fileContent, QString suggestedFilename) +{ + QJniObject::callStaticMethod(CLASSNAME, "saveFileAs", "(Ljava/lang/String;Ljava/lang/String;)V", + QJniObject::fromString(fileContent).object(), + QJniObject::fromString(suggestedFilename).object()); } // static -AndroidVPNActivity* AndroidVPNActivity::instance() { +AndroidVPNActivity *AndroidVPNActivity::instance() +{ if (s_instance == nullptr) { AndroidVPNActivity::maybeInit(); } @@ -75,21 +79,19 @@ AndroidVPNActivity* AndroidVPNActivity::instance() { } // static -void AndroidVPNActivity::sendToService(ServiceAction type, const QString& data) { +void AndroidVPNActivity::sendToService(ServiceAction type, const QString &data) +{ int messageType = (int)type; - QJniObject::callStaticMethod( - CLASSNAME, - "sendToService", "(ILjava/lang/String;)V", - static_cast(messageType), - QJniObject::fromString(data).object()); + QJniObject::callStaticMethod(CLASSNAME, "sendToService", "(ILjava/lang/String;)V", + static_cast(messageType), QJniObject::fromString(data).object()); } // static -void AndroidVPNActivity::onServiceMessage(JNIEnv* env, jobject thiz, - jint messageType, jstring body) { +void AndroidVPNActivity::onServiceMessage(JNIEnv *env, jobject thiz, jint messageType, jstring body) +{ Q_UNUSED(thiz); - const char* buffer = env->GetStringUTFChars(body, nullptr); + const char *buffer = env->GetStringUTFChars(body, nullptr); if (!buffer) { return; } @@ -97,38 +99,23 @@ void AndroidVPNActivity::onServiceMessage(JNIEnv* env, jobject thiz, QString parcelBody(buffer); env->ReleaseStringUTFChars(body, buffer); AndroidUtils::dispatchToMainThread([messageType, parcelBody] { - AndroidVPNActivity::instance()->handleServiceMessage(messageType, - parcelBody); + AndroidVPNActivity::instance()->handleServiceMessage(messageType, parcelBody); }); } -void AndroidVPNActivity::handleServiceMessage(int code, const QString& data) { +void AndroidVPNActivity::handleServiceMessage(int code, const QString &data) +{ auto mode = (ServiceEvents)code; switch (mode) { - case ServiceEvents::EVENT_INIT: - emit eventInitialized(data); - break; - case ServiceEvents::EVENT_CONNECTED: - emit eventConnected(data); - break; - case ServiceEvents::EVENT_DISCONNECTED: - emit eventDisconnected(data); - break; - case ServiceEvents::EVENT_STATISTIC_UPDATE: - emit eventStatisticUpdate(data); - break; - case ServiceEvents::EVENT_BACKEND_LOGS: - emit eventBackendLogs(data); - break; - case ServiceEvents::EVENT_ACTIVATION_ERROR: - emit eventActivationError(data); - break; - case ServiceEvents::EVENT_CONFIG_IMPORT: - emit eventConfigImport(data); - break; - default: - Q_ASSERT(false); + case ServiceEvents::EVENT_INIT: emit eventInitialized(data); break; + case ServiceEvents::EVENT_CONNECTED: emit eventConnected(data); break; + case ServiceEvents::EVENT_DISCONNECTED: emit eventDisconnected(data); break; + case ServiceEvents::EVENT_STATISTIC_UPDATE: emit eventStatisticUpdate(data); break; + case ServiceEvents::EVENT_BACKEND_LOGS: emit eventBackendLogs(data); break; + case ServiceEvents::EVENT_ACTIVATION_ERROR: emit eventActivationError(data); break; + case ServiceEvents::EVENT_CONFIG_IMPORT: emit eventConfigImport(data); break; + default: Q_ASSERT(false); } } @@ -137,22 +124,21 @@ void AndroidVPNActivity::handleActivityMessage(int code, const QString &data) auto mode = (UIEvents)code; switch (mode) { - case UIEvents::QR_CODED_DECODED: - emit eventQrCodeReceived(data); - break; - default: - Q_ASSERT(false); + case UIEvents::QR_CODED_DECODED: emit eventQrCodeReceived(data); break; + default: Q_ASSERT(false); } } -void AndroidVPNActivity::onServiceConnected(JNIEnv* env, jobject thiz) { +void AndroidVPNActivity::onServiceConnected(JNIEnv *env, jobject thiz) +{ Q_UNUSED(env); Q_UNUSED(thiz); emit AndroidVPNActivity::instance()->serviceConnected(); } -void AndroidVPNActivity::onServiceDisconnected(JNIEnv* env, jobject thiz) { +void AndroidVPNActivity::onServiceDisconnected(JNIEnv *env, jobject thiz) +{ Q_UNUSED(env); Q_UNUSED(thiz); @@ -162,7 +148,7 @@ void AndroidVPNActivity::onServiceDisconnected(JNIEnv* env, jobject thiz) { void AndroidVPNActivity::onAndroidVpnActivityMessage(JNIEnv *env, jobject thiz, jint messageType, jstring message) { Q_UNUSED(thiz); - const char* buffer = env->GetStringUTFChars(message, nullptr); + const char *buffer = env->GetStringUTFChars(message, nullptr); if (!buffer) { return; } diff --git a/client/resources.qrc b/client/resources.qrc index 782b6446..d0ff176f 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -56,75 +56,6 @@ server_scripts/wireguard/template.conf server_scripts/website_tor/configure_container.sh server_scripts/website_tor/run_container.sh - ui/qml/main.qml - ui/qml/Pages/PageBase.qml - ui/qml/Pages/PageAppSetting.qml - ui/qml/Pages/PageGeneralSettings.qml - ui/qml/Pages/PageNetworkSetting.qml - ui/qml/Pages/PageNewServer.qml - ui/qml/Pages/PageServerConfiguringProgress.qml - ui/qml/Pages/PageNewServerProtocols.qml - ui/qml/Pages/PageServerList.qml - ui/qml/Pages/PageServerContainers.qml - ui/qml/Pages/PageServerSettings.qml - ui/qml/Pages/PageSetupWizard.qml - ui/qml/Pages/PageSetupWizardHighLevel.qml - ui/qml/Pages/PageSetupWizardLowLevel.qml - ui/qml/Pages/PageSetupWizardMediumLevel.qml - ui/qml/Pages/PageSetupWizardVPNMode.qml - ui/qml/Pages/PageShareConnection.qml - ui/qml/Pages/PageSites.qml - ui/qml/Pages/PageStart.qml - ui/qml/Pages/PageVPN.qml - ui/qml/Pages/PageAbout.qml - ui/qml/Pages/PageQrDecoderIos.qml - ui/qml/Pages/PageViewConfig.qml - ui/qml/Pages/PageClientManagement.qml - ui/qml/Pages/ClientInfo/PageClientInfoBase.qml - ui/qml/Pages/ClientInfo/PageClientInfoOpenVPN.qml - ui/qml/Pages/ClientInfo/PageClientInfoWireGuard.qml - ui/qml/Pages/Protocols/PageProtoCloak.qml - ui/qml/Pages/Protocols/PageProtoOpenVPN.qml - ui/qml/Pages/Protocols/PageProtoShadowSocks.qml - ui/qml/Pages/Protocols/PageProtoSftp.qml - ui/qml/Pages/Protocols/PageProtoTorWebSite.qml - ui/qml/Pages/Protocols/PageProtocolBase.qml - ui/qml/Pages/Protocols/PageProtoWireGuard.qml - ui/qml/Pages/InstallSettings/InstallSettingsBase.qml - ui/qml/Pages/InstallSettings/SelectContainer.qml - ui/qml/Pages/Share/PageShareProtoCloak.qml - ui/qml/Pages/Share/PageShareProtocolBase.qml - ui/qml/Pages/Share/PageShareProtoOpenVPN.qml - ui/qml/Pages/Share/PageShareProtoSftp.qml - ui/qml/Pages/Share/PageShareProtoShadowSocks.qml - ui/qml/Pages/Share/PageShareProtoTorWebSite.qml - ui/qml/Pages/Share/PageShareProtoAmnezia.qml - ui/qml/Pages/Share/PageShareProtoWireGuard.qml - ui/qml/Pages/Share/PageShareProtoIkev2.qml - ui/qml/Controls/BasicButtonType.qml - ui/qml/Controls/BlueButtonType.qml - ui/qml/Controls/CheckBoxType.qml - ui/qml/Controls/ComboBoxType.qml - ui/qml/Controls/ImageButtonType.qml - ui/qml/Controls/LabelType.qml - ui/qml/Controls/RadioButtonType.qml - ui/qml/Controls/SettingButtonType.qml - ui/qml/Controls/ShareConnectionButtonType.qml - ui/qml/Controls/ShareConnectionContent.qml - ui/qml/Controls/TextFieldType.qml - ui/qml/Controls/RichLabelType.qml - ui/qml/Controls/SvgImageType.qml - ui/qml/Controls/FlickableType.qml - ui/qml/Controls/UrlButtonType.qml - ui/qml/Controls/TextAreaType.qml - ui/qml/Controls/ContextMenu.qml - ui/qml/Controls/FadeBehavior.qml - ui/qml/Controls/VisibleBehavior.qml - ui/qml/Controls/Caption.qml - ui/qml/Controls/Logo.qml - ui/qml/Controls/BackButton.qml - ui/qml/Controls/ShareConnectionButtonCopyType.qml - ui/qml/Controls/SvgButtonType.qml ui/qml/Config/GlobalConfig.qml ui/qml/Config/qmldir server_scripts/check_server_is_busy.sh @@ -161,10 +92,6 @@ images/svg/control_point_black_24dp.svg images/svg/settings_suggest_black_24dp.svg server_scripts/website_tor/Dockerfile - ui/qml/Controls/PopupWithQuestion.qml - ui/qml/Pages/PageAdvancedServerSettings.qml - ui/qml/Controls/PopupWarning.qml - ui/qml/Controls/PopupWithTextField.qml server_scripts/check_user_in_sudo.sh ui/qml/Controls2/BasicButtonType.qml ui/qml/Controls2/TextFieldWithHeaderType.qml diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index d3813086..0754b024 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -1,6 +1,10 @@ #include "connectionController.h" -#include +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) + #include +#else + #include +#endif #include "core/errorstrings.h" diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/pageController.cpp index 84d2ebf2..46a1b1fd 100644 --- a/client/ui/controllers/pageController.cpp +++ b/client/ui/controllers/pageController.cpp @@ -1,6 +1,11 @@ #include "pageController.h" -#include +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) + #include +#else + #include +#endif + #ifdef Q_OS_ANDROID #include "../../platforms/android/androidutils.h" #include diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index 203d53bd..7c7402e0 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -2,10 +2,6 @@ #include -#ifdef Q_OS_IOS - #include -#endif - #include "fileUtilites.h" #include "logger.h" #include "ui/qautostart.h" diff --git a/client/ui/controllers/sitesController.cpp b/client/ui/controllers/sitesController.cpp index 16225aa8..5821b371 100644 --- a/client/ui/controllers/sitesController.cpp +++ b/client/ui/controllers/sitesController.cpp @@ -1,12 +1,9 @@ #include "sitesController.h" +#include #include #include -#ifdef Q_OS_IOS - #include -#endif - #include "fileUtilites.h" #include "utilities.h" diff --git a/client/ui/pages_logic/AdvancedServerSettingsLogic.cpp b/client/ui/pages_logic/AdvancedServerSettingsLogic.cpp deleted file mode 100644 index a96827b6..00000000 --- a/client/ui/pages_logic/AdvancedServerSettingsLogic.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include "AdvancedServerSettingsLogic.h" - -#include "VpnLogic.h" -#include "ui/uilogic.h" -#include "core/errorstrings.h" -#include "core/servercontroller.h" - -AdvancedServerSettingsLogic::AdvancedServerSettingsLogic(UiLogic *uiLogic, QObject *parent): PageLogicBase(uiLogic, parent), - m_labelWaitInfoVisible{true}, - m_pushButtonClearVisible{true}, - m_pushButtonClearText{tr("Clear server from Amnezia software")} -{ -} - -void AdvancedServerSettingsLogic::onUpdatePage() -{ - set_labelWaitInfoVisible(false); - set_labelWaitInfoText(""); - set_pushButtonClearVisible(m_settings->haveAuthData(uiLogic()->m_selectedServerIndex)); - const QJsonObject &server = m_settings->server(uiLogic()->m_selectedServerIndex); - const QString &port = server.value(config_key::port).toString(); - - const QString &userName = server.value(config_key::userName).toString(); - const QString &hostName = server.value(config_key::hostName).toString(); - QString name = QString("%1%2%3%4%5").arg(userName, - userName.isEmpty() ? "" : "@", - hostName, - port.isEmpty() ? "" : ":", - port); - - set_labelServerText(name); - - DockerContainer selectedContainer = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex); - QString selectedContainerName = ContainerProps::containerHumanNames().value(selectedContainer); - set_labelCurrentVpnProtocolText(tr("Service: ") + selectedContainerName); -} - -void AdvancedServerSettingsLogic::onPushButtonClearServerClicked() -{ - set_pageEnabled(false); - set_pushButtonClearText(tr("Uninstalling Amnezia software...")); - - if (m_settings->defaultServerIndex() == uiLogic()->m_selectedServerIndex) { - uiLogic()->pageLogic()->onDisconnect(); - } - - ServerController serverController(m_settings); - ErrorCode errorCode = serverController.removeAllContainers(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex)); - if (errorCode) { - emit uiLogic()->showWarningMessage(tr("Error occurred while cleaning the server.") + "\n" + - tr("Error message: ") + errorString(errorCode) + "\n" + - tr("See logs for details.")); - } else { - set_labelWaitInfoVisible(true); - set_labelWaitInfoText(tr("Amnezia server successfully uninstalled")); - } - - m_settings->setContainers(uiLogic()->m_selectedServerIndex, {}); - m_settings->setDefaultContainer(uiLogic()->m_selectedServerIndex, DockerContainer::None); - - set_pageEnabled(true); - set_pushButtonClearText(tr("Clear server from Amnezia software")); -} - -void AdvancedServerSettingsLogic::onPushButtonScanServerClicked() -{ - set_labelWaitInfoVisible(false); - set_pageEnabled(false); - - bool isServerCreated; - auto containersCount = m_settings->containers(uiLogic()->m_selectedServerIndex).size(); - ErrorCode errorCode = uiLogic()->addAlreadyInstalledContainersGui(isServerCreated); - if (errorCode != ErrorCode::NoError) { - emit uiLogic()->showWarningMessage(tr("Error occurred while scanning the server.") + "\n" + - tr("Error message: ") + errorString(errorCode) + "\n" + - tr("See logs for details.")); - } - auto newContainersCount = m_settings->containers(uiLogic()->m_selectedServerIndex).size(); - if (containersCount != newContainersCount) { - emit uiLogic()->showWarningMessage(tr("All containers installed on the server are added to the GUI")); - } else { - emit uiLogic()->showWarningMessage(tr("No installed containers found on the server")); - } - - - onUpdatePage(); - set_pageEnabled(true); -} diff --git a/client/ui/pages_logic/AdvancedServerSettingsLogic.h b/client/ui/pages_logic/AdvancedServerSettingsLogic.h deleted file mode 100644 index 692968f1..00000000 --- a/client/ui/pages_logic/AdvancedServerSettingsLogic.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef ADVANCEDSERVERSETTINGSLOGIC_H -#define ADVANCEDSERVERSETTINGSLOGIC_H - -#include "PageLogicBase.h" - -class UiLogic; - -class AdvancedServerSettingsLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(bool, labelWaitInfoVisible) - AUTO_PROPERTY(QString, labelWaitInfoText) - - AUTO_PROPERTY(QString, pushButtonClearText) - AUTO_PROPERTY(bool, pushButtonClearVisible) - - AUTO_PROPERTY(QString, labelServerText) - AUTO_PROPERTY(QString, labelCurrentVpnProtocolText) - -public: - explicit AdvancedServerSettingsLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~AdvancedServerSettingsLogic() = default; - - Q_INVOKABLE void onUpdatePage() override; - - Q_INVOKABLE void onPushButtonClearServerClicked(); - Q_INVOKABLE void onPushButtonScanServerClicked(); -}; - -#endif // ADVANCEDSERVERSETTINGSLOGIC_H diff --git a/client/ui/pages_logic/AppSettingsLogic.cpp b/client/ui/pages_logic/AppSettingsLogic.cpp deleted file mode 100644 index 044b97b8..00000000 --- a/client/ui/pages_logic/AppSettingsLogic.cpp +++ /dev/null @@ -1,119 +0,0 @@ -#include "AppSettingsLogic.h" - -#include "logger.h" -#include "version.h" -#include "ui/qautostart.h" -#include "ui/uilogic.h" - -#include -#include -#include -#include - -#ifdef Q_OS_IOS -#include -#endif - -using namespace amnezia; -using namespace PageEnumNS; - -AppSettingsLogic::AppSettingsLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent), - m_checkBoxAutostartChecked{false}, - m_checkBoxAutoConnectChecked{false}, - m_checkBoxStartMinimizedChecked{false}, - m_checkBoxSaveLogsChecked{false} -{ - -} - -void AppSettingsLogic::onUpdatePage() -{ - set_checkBoxAutostartChecked(Autostart::isAutostart()); - set_checkBoxAutoConnectChecked(m_settings->isAutoConnect()); - set_checkBoxStartMinimizedChecked(m_settings->isStartMinimized()); - set_checkBoxSaveLogsChecked(m_settings->isSaveLogs()); - - QString ver = QString("%1: %2 (%3)") - .arg(tr("Software version")) - .arg(QString(APP_MAJOR_VERSION)) - .arg(__DATE__); - set_labelVersionText(ver); -} - -void AppSettingsLogic::onCheckBoxAutostartToggled(bool checked) -{ - if (!checked) { - set_checkBoxAutoConnectChecked(false); - } - Autostart::setAutostart(checked); -} - -void AppSettingsLogic::onCheckBoxAutoconnectToggled(bool checked) -{ - m_settings->setAutoConnect(checked); -} - -void AppSettingsLogic::onCheckBoxStartMinimizedToggled(bool checked) -{ - m_settings->setStartMinimized(checked); -} - -void AppSettingsLogic::onCheckBoxSaveLogsCheckedToggled(bool checked) -{ - m_settings->setSaveLogs(checked); -} - -void AppSettingsLogic::onPushButtonOpenLogsClicked() -{ - Logger::openLogsFolder(); -} - -void AppSettingsLogic::onPushButtonExportLogsClicked() -{ - uiLogic()->saveTextFile(tr("Save log"), "AmneziaVPN.log", ".log", Logger::getLogFile()); -} - -void AppSettingsLogic::onPushButtonClearLogsClicked() -{ - Logger::clearLogs(); - Logger::clearServiceLogs(); -} - -void AppSettingsLogic::onPushButtonBackupAppConfigClicked() -{ - uiLogic()->saveTextFile("Backup application config", "AmneziaVPN.backup", ".backup", m_settings->backupAppConfig()); -} - -void AppSettingsLogic::onPushButtonRestoreAppConfigClicked() -{ - QString fileName = UiLogic::getOpenFileName(Q_NULLPTR, tr("Open backup"), - QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*.backup"); - - if (fileName.isEmpty()) return; - - QFile file(fileName); - -#ifdef Q_OS_IOS - CFURLRef url = CFURLCreateWithFileSystemPath( - kCFAllocatorDefault, CFStringCreateWithCharacters(0, reinterpret_cast(fileName.unicode()), - fileName.length()), - kCFURLPOSIXPathStyle, 0); - - if (!CFURLStartAccessingSecurityScopedResource(url)) { - qDebug() << "Could not access path " << QUrl::fromLocalFile(fileName).toString(); - } -#endif - - file.open(QIODevice::ReadOnly); - QByteArray data = file.readAll(); - - bool ok = m_settings->restoreAppConfig(data); - if (ok) { - emit uiLogic()->goToPage(Page::Vpn); - emit uiLogic()->setStartPage(Page::Vpn); - } else { - emit uiLogic()->showWarningMessage(tr("Can't import config, file is corrupted.")); - } -} - diff --git a/client/ui/pages_logic/AppSettingsLogic.h b/client/ui/pages_logic/AppSettingsLogic.h deleted file mode 100644 index fc9f0da7..00000000 --- a/client/ui/pages_logic/AppSettingsLogic.h +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef APP_SETTINGS_LOGIC_H -#define APP_SETTINGS_LOGIC_H - -#include "PageLogicBase.h" - -class UiLogic; - -class AppSettingsLogic : public PageLogicBase -{ - Q_OBJECT - AUTO_PROPERTY(bool, checkBoxAutostartChecked) - AUTO_PROPERTY(bool, checkBoxAutoConnectChecked) - AUTO_PROPERTY(bool, checkBoxStartMinimizedChecked) - AUTO_PROPERTY(bool, checkBoxSaveLogsChecked) - AUTO_PROPERTY(QString, labelVersionText) - -public: - Q_INVOKABLE void onUpdatePage() override; - - Q_INVOKABLE void onCheckBoxAutostartToggled(bool checked); - Q_INVOKABLE void onCheckBoxAutoconnectToggled(bool checked); - Q_INVOKABLE void onCheckBoxStartMinimizedToggled(bool checked); - Q_INVOKABLE void onCheckBoxSaveLogsCheckedToggled(bool checked); - Q_INVOKABLE void onPushButtonOpenLogsClicked(); - Q_INVOKABLE void onPushButtonExportLogsClicked(); - Q_INVOKABLE void onPushButtonClearLogsClicked(); - - Q_INVOKABLE void onPushButtonBackupAppConfigClicked(); - Q_INVOKABLE void onPushButtonRestoreAppConfigClicked(); - - -public: - explicit AppSettingsLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~AppSettingsLogic() = default; - -}; -#endif // APP_SETTINGS_LOGIC_H diff --git a/client/ui/pages_logic/ClientInfoLogic.cpp b/client/ui/pages_logic/ClientInfoLogic.cpp deleted file mode 100644 index dca0b9fd..00000000 --- a/client/ui/pages_logic/ClientInfoLogic.cpp +++ /dev/null @@ -1,213 +0,0 @@ -#include "ClientInfoLogic.h" - -#include - -#include "version.h" -#include "core/errorstrings.h" -#include "core/servercontroller.h" -#include "ui/models/clientManagementModel.h" -#include "ui/uilogic.h" - -namespace { - bool isErrorOccured(ErrorCode error) { - if (error != ErrorCode::NoError) { - QMessageBox::warning(nullptr, APPLICATION_NAME, - QObject::tr("An error occurred while saving the list of clients.") + "\n" + errorString(error)); - return true; - } - return false; - } -} - -ClientInfoLogic::ClientInfoLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent) -{ - -} - -void ClientInfoLogic::setCurrentClientId(int index) -{ - m_currentClientIndex = index; -} - -void ClientInfoLogic::onUpdatePage() -{ - set_pageContentVisible(false); - set_busyIndicatorIsRunning(true); - - const ServerCredentials credentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex); - const DockerContainer container = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex); - const QString containerNameString = ContainerProps::containerHumanNames().value(container); - set_labelCurrentVpnProtocolText(tr("Service: ") + containerNameString); - - const QVector protocols = ContainerProps::protocolsForContainer(container); - if (!protocols.empty()) { - const Proto currentMainProtocol = protocols.front(); - - auto model = qobject_cast(uiLogic()->clientManagementModel()); - const QModelIndex modelIndex = model->index(m_currentClientIndex); - - set_lineEditNameAliasText(model->data(modelIndex, ClientManagementModel::ClientRoles::NameRole).toString()); - if (currentMainProtocol == Proto::OpenVpn) { - const QString certId = model->data(modelIndex, ClientManagementModel::ClientRoles::OpenVpnCertIdRole).toString(); - QString certData = model->data(modelIndex, ClientManagementModel::ClientRoles::OpenVpnCertDataRole).toString(); - - if (certData.isEmpty() && !certId.isEmpty()) { - QString stdOut; - auto cbReadStdOut = [&](const QString &data, libssh::Client &) { - stdOut += data + "\n"; - return ErrorCode::NoError; - }; - - const QString getOpenVpnCertData = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'cat /opt/amnezia/openvpn/pki/issued/%1.crt'") - .arg(certId); - ServerController serverController(m_settings); - const QString script = serverController.replaceVars(getOpenVpnCertData, serverController.genVarsForScript(credentials, container)); - ErrorCode error = serverController.runScript(credentials, script, cbReadStdOut); - certData = stdOut; - if (isErrorOccured(error)) { - set_busyIndicatorIsRunning(false); - emit uiLogic()->closePage(); - return; - } - } - set_labelOpenVpnCertId(certId); - set_textAreaOpenVpnCertData(certData); - } else if (currentMainProtocol == Proto::WireGuard) { - set_textAreaWireGuardKeyData(model->data(modelIndex, ClientManagementModel::ClientRoles::WireGuardPublicKey).toString()); - } - } - set_pageContentVisible(true); - set_busyIndicatorIsRunning(false); -} - -void ClientInfoLogic::onLineEditNameAliasEditingFinished() -{ - set_busyIndicatorIsRunning(true); - - auto model = qobject_cast(uiLogic()->clientManagementModel()); - const QModelIndex modelIndex = model->index(m_currentClientIndex); - model->setData(modelIndex, m_lineEditNameAliasText, ClientManagementModel::ClientRoles::NameRole); - - const DockerContainer selectedContainer = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex); - const ServerCredentials credentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex); - const QVector protocols = ContainerProps::protocolsForContainer(selectedContainer); - if (!protocols.empty()) { - const Proto currentMainProtocol = protocols.front(); - const QJsonObject clientsTable = model->getContent(currentMainProtocol); - ErrorCode error = setClientsList(credentials, - selectedContainer, - currentMainProtocol, - clientsTable); - isErrorOccured(error); - } - - set_busyIndicatorIsRunning(false); -} - -void ClientInfoLogic::onRevokeOpenVpnCertificateClicked() -{ - set_busyIndicatorIsRunning(true); - const DockerContainer container = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex); - const ServerCredentials credentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex); - - auto model = qobject_cast(uiLogic()->clientManagementModel()); - const QModelIndex modelIndex = model->index(m_currentClientIndex); - const QString certId = model->data(modelIndex, ClientManagementModel::ClientRoles::OpenVpnCertIdRole).toString(); - - const QString getOpenVpnCertData = QString("sudo docker exec -i $CONTAINER_NAME bash -c '" - "cd /opt/amnezia/openvpn ;\\" - "easyrsa revoke %1 ;\\" - "easyrsa gen-crl ;\\" - "cp pki/crl.pem .'").arg(certId); - ServerController serverController(m_settings); - const QString script = serverController.replaceVars(getOpenVpnCertData, - serverController.genVarsForScript(credentials, container)); - auto error = serverController.runScript(credentials, script); - if (isErrorOccured(error)) { - set_busyIndicatorIsRunning(false); - emit uiLogic()->goToPage(Page::ServerSettings); - return; - } - - model->removeRows(m_currentClientIndex); - const QJsonObject clientsTable = model->getContent(Proto::OpenVpn); - error = setClientsList(credentials, container, Proto::OpenVpn, clientsTable); - if (isErrorOccured(error)) { - set_busyIndicatorIsRunning(false); - return; - } - - const QJsonObject &containerConfig = m_settings->containerConfig(uiLogic()->m_selectedServerIndex, container); - error = serverController.startupContainerWorker(credentials, container, containerConfig); - if (isErrorOccured(error)) { - set_busyIndicatorIsRunning(false); - return; - } - - set_busyIndicatorIsRunning(false); -} - -void ClientInfoLogic::onRevokeWireGuardKeyClicked() -{ - set_busyIndicatorIsRunning(true); - ErrorCode error; - const DockerContainer container = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex); - const ServerCredentials credentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex); - - ServerController serverController(m_settings); - - const QString wireGuardConfigFile = "opt/amnezia/wireguard/wg0.conf"; - const QString wireguardConfigString = serverController.getTextFileFromContainer(container, credentials, wireGuardConfigFile, &error); - if (isErrorOccured(error)) { - set_busyIndicatorIsRunning(false); - return; - } - - auto model = qobject_cast(uiLogic()->clientManagementModel()); - const QModelIndex modelIndex = model->index(m_currentClientIndex); - const QString key = model->data(modelIndex, ClientManagementModel::ClientRoles::WireGuardPublicKey).toString(); - - auto configSections = wireguardConfigString.split("[", Qt::SkipEmptyParts); - for (auto §ion : configSections) { - if (section.contains(key)) { - configSections.removeOne(section); - } - } - QString newWireGuardConfig = configSections.join("["); - newWireGuardConfig.insert(0, "["); - error = serverController.uploadTextFileToContainer(container, credentials, newWireGuardConfig, - protocols::wireguard::serverConfigPath, - libssh::SftpOverwriteMode::SftpOverwriteExisting); - if (isErrorOccured(error)) { - set_busyIndicatorIsRunning(false); - return; - } - - model->removeRows(m_currentClientIndex); - const QJsonObject clientsTable = model->getContent(Proto::WireGuard); - error = setClientsList(credentials, container, Proto::WireGuard, clientsTable); - if (isErrorOccured(error)) { - set_busyIndicatorIsRunning(false); - return; - } - - const QString script = "sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip /opt/amnezia/wireguard/wg0.conf)'"; - error = serverController.runScript(credentials, - serverController.replaceVars(script, serverController.genVarsForScript(credentials, container))); - if (isErrorOccured(error)) { - set_busyIndicatorIsRunning(false); - return; - } - - set_busyIndicatorIsRunning(false); -} - -ErrorCode ClientInfoLogic::setClientsList(const ServerCredentials &credentials, DockerContainer container, Proto mainProtocol, const QJsonObject &clietns) -{ - const QString mainProtocolString = ProtocolProps::protoToString(mainProtocol); - const QString clientsTableFile = QString("opt/amnezia/%1/clientsTable").arg(mainProtocolString); - ServerController serverController(m_settings); - ErrorCode error = serverController.uploadTextFileToContainer(container, credentials, QJsonDocument(clietns).toJson(), clientsTableFile); - return error; -} diff --git a/client/ui/pages_logic/ClientInfoLogic.h b/client/ui/pages_logic/ClientInfoLogic.h deleted file mode 100644 index 5ba19887..00000000 --- a/client/ui/pages_logic/ClientInfoLogic.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef CLIENTINFOLOGIC_H -#define CLIENTINFOLOGIC_H - -#include "PageLogicBase.h" - -#include "core/defs.h" -#include "containers/containers_defs.h" -#include "protocols/protocols_defs.h" - -class UiLogic; - -class ClientInfoLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(QString, lineEditNameAliasText) - AUTO_PROPERTY(QString, labelOpenVpnCertId) - AUTO_PROPERTY(QString, textAreaOpenVpnCertData) - AUTO_PROPERTY(QString, labelCurrentVpnProtocolText) - AUTO_PROPERTY(QString, textAreaWireGuardKeyData) - AUTO_PROPERTY(bool, busyIndicatorIsRunning); - AUTO_PROPERTY(bool, pageContentVisible); - -public: - ClientInfoLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~ClientInfoLogic() = default; - - void setCurrentClientId(int index); - -public slots: - void onUpdatePage() override; - void onLineEditNameAliasEditingFinished(); - void onRevokeOpenVpnCertificateClicked(); - void onRevokeWireGuardKeyClicked(); - -private: - ErrorCode setClientsList(const ServerCredentials &credentials, DockerContainer container, Proto mainProtocol, const QJsonObject &clietns); - - int m_currentClientIndex; -}; - -#endif // CLIENTINFOLOGIC_H diff --git a/client/ui/pages_logic/ClientManagementLogic.cpp b/client/ui/pages_logic/ClientManagementLogic.cpp deleted file mode 100644 index 673ee9ab..00000000 --- a/client/ui/pages_logic/ClientManagementLogic.cpp +++ /dev/null @@ -1,143 +0,0 @@ -#include "ClientManagementLogic.h" - -#include - -#include "version.h" -#include "core/errorstrings.h" -#include "core/servercontroller.h" -#include "ui/pages_logic/ClientInfoLogic.h" -#include "ui/models/clientManagementModel.h" -#include "ui/uilogic.h" - -ClientManagementLogic::ClientManagementLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent) -{ - -} - -void ClientManagementLogic::onUpdatePage() -{ - set_busyIndicatorIsRunning(true); - - qobject_cast(uiLogic()->clientManagementModel())->clearData(); - DockerContainer selectedContainer = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex); - QString selectedContainerName = ContainerProps::containerHumanNames().value(selectedContainer); - set_labelCurrentVpnProtocolText(tr("Service: ") + selectedContainerName); - - QJsonObject clients; - - auto protocols = ContainerProps::protocolsForContainer(selectedContainer); - if (!protocols.empty()) { - m_currentMainProtocol = protocols.front(); - - const ServerCredentials credentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex); - - ErrorCode error = getClientsList(credentials, selectedContainer, m_currentMainProtocol, clients); - if (error != ErrorCode::NoError) { - QMessageBox::warning(nullptr, APPLICATION_NAME, - tr("An error occurred while getting the list of clients.") + "\n" + errorString(error)); - set_busyIndicatorIsRunning(false); - return; - } - } - QVector clientsArray; - for (auto &clientId : clients.keys()) { - clientsArray.push_back(clients[clientId].toObject()); - } - qobject_cast(uiLogic()->clientManagementModel())->setContent(clientsArray); - - set_busyIndicatorIsRunning(false); -} - -void ClientManagementLogic::onClientItemClicked(int index) -{ - uiLogic()->pageLogic()->setCurrentClientId(index); - emit uiLogic()->goToClientInfoPage(m_currentMainProtocol); -} - -ErrorCode ClientManagementLogic::getClientsList(const ServerCredentials &credentials, DockerContainer container, Proto mainProtocol, QJsonObject &clietns) -{ - ErrorCode error = ErrorCode::NoError; - QString stdOut; - auto cbReadStdOut = [&](const QString &data, libssh::Client &) { - stdOut += data + "\n"; - return ErrorCode::NoError; - }; - - const QString mainProtocolString = ProtocolProps::protoToString(mainProtocol); - - ServerController serverController(m_settings); - - const QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable").arg(mainProtocolString); - const QByteArray clientsTableString = serverController.getTextFileFromContainer(container, credentials, clientsTableFile, &error); - if (error != ErrorCode::NoError) { - return error; - } - QJsonObject clientsTable = QJsonDocument::fromJson(clientsTableString).object(); - int count = 0; - - if (mainProtocol == Proto::OpenVpn) { - const QString getOpenVpnClientsList = "sudo docker exec -i $CONTAINER_NAME bash -c 'ls /opt/amnezia/openvpn/pki/issued'"; - QString script = serverController.replaceVars(getOpenVpnClientsList, serverController.genVarsForScript(credentials, container)); - error = serverController.runScript(credentials, script, cbReadStdOut); - if (error != ErrorCode::NoError) { - return error; - } - - if (!stdOut.isEmpty()) { - QStringList certsIds = stdOut.split("\n", Qt::SkipEmptyParts); - certsIds.removeAll("AmneziaReq.crt"); - - for (auto &openvpnCertId : certsIds) { - openvpnCertId.replace(".crt", ""); - if (!clientsTable.contains(openvpnCertId)) { - - QJsonObject client; - client["openvpnCertId"] = openvpnCertId; - client["clientName"] = QString("Client %1").arg(count); - client["openvpnCertData"] = ""; - clientsTable[openvpnCertId] = client; - count++; - } - } - } - } else if (mainProtocol == Proto::WireGuard) { - const QString wireGuardConfigFile = "opt/amnezia/wireguard/wg0.conf"; - const QString wireguardConfigString = serverController.getTextFileFromContainer(container, credentials, wireGuardConfigFile, &error); - if (error != ErrorCode::NoError) { - return error; - } - - auto configLines = wireguardConfigString.split("\n", Qt::SkipEmptyParts); - QStringList wireguardKeys; - for (const auto &line : configLines) { - auto configPair = line.split(" = ", Qt::SkipEmptyParts); - if (configPair.front() == "PublicKey") { - wireguardKeys.push_back(configPair.back()); - } - } - - for (auto &wireguardKey : wireguardKeys) { - if (!clientsTable.contains(wireguardKey)) { - QJsonObject client; - client["clientName"] = QString("Client %1").arg(count); - client["wireguardPublicKey"] = wireguardKey; - clientsTable[wireguardKey] = client; - count++; - } - } - } - - const QByteArray newClientsTableString = QJsonDocument(clientsTable).toJson(); - if (clientsTableString != newClientsTableString) { - error = serverController.uploadTextFileToContainer(container, credentials, newClientsTableString, clientsTableFile); - } - - if (error != ErrorCode::NoError) { - return error; - } - - clietns = clientsTable; - - return error; -} diff --git a/client/ui/pages_logic/ClientManagementLogic.h b/client/ui/pages_logic/ClientManagementLogic.h deleted file mode 100644 index 9c181716..00000000 --- a/client/ui/pages_logic/ClientManagementLogic.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef CLIENTMANAGMENTLOGIC_H -#define CLIENTMANAGMENTLOGIC_H - -#include "PageLogicBase.h" - -#include "core/defs.h" -#include "containers/containers_defs.h" -#include "protocols/protocols_defs.h" - -class UiLogic; - -class ClientManagementLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(QString, labelCurrentVpnProtocolText) - AUTO_PROPERTY(bool, busyIndicatorIsRunning); - -public: - ClientManagementLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~ClientManagementLogic() = default; - -public slots: - void onUpdatePage() override; - void onClientItemClicked(int index); - -private: - ErrorCode getClientsList(const ServerCredentials &credentials, DockerContainer container, Proto mainProtocol, QJsonObject &clietns); - - amnezia::Proto m_currentMainProtocol; -}; - -#endif // CLIENTMANAGMENTLOGIC_H diff --git a/client/ui/pages_logic/GeneralSettingsLogic.cpp b/client/ui/pages_logic/GeneralSettingsLogic.cpp deleted file mode 100644 index 141308f4..00000000 --- a/client/ui/pages_logic/GeneralSettingsLogic.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include "GeneralSettingsLogic.h" -#include "ShareConnectionLogic.h" - -#include "../models/protocols_model.h" -#include "../uilogic.h" - -GeneralSettingsLogic::GeneralSettingsLogic(UiLogic *logic, QObject *parent) : PageLogicBase(logic, parent) -{ -} - -void GeneralSettingsLogic::onUpdatePage() -{ - uiLogic()->m_selectedServerIndex = m_settings->defaultServerIndex(); - set_existsAnyServer(m_settings->serversCount() > 0); - uiLogic()->m_selectedDockerContainer = m_settings->defaultContainer(m_settings->defaultServerIndex()); - - set_pushButtonGeneralSettingsShareConnectionEnable(m_settings->haveAuthData(m_settings->defaultServerIndex())); -} - -void GeneralSettingsLogic::onPushButtonGeneralSettingsServerSettingsClicked() -{ - uiLogic()->m_selectedServerIndex = m_settings->defaultServerIndex(); - uiLogic()->m_selectedDockerContainer = m_settings->defaultContainer(m_settings->defaultServerIndex()); - - emit uiLogic()->goToPage(Page::ServerSettings); -} - -void GeneralSettingsLogic::onPushButtonGeneralSettingsShareConnectionClicked() -{ - uiLogic()->m_selectedServerIndex = m_settings->defaultServerIndex(); - uiLogic()->m_selectedDockerContainer = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex); - - // qobject_cast(uiLogic()->protocolsModel())->setSelectedServerIndex(uiLogic()->m_selectedServerIndex); qobject_cast(uiLogic()->protocolsModel())->setSelectedDockerContainer(uiLogic()->m_selectedDockerContainer); - - uiLogic()->pageLogic()->updateSharingPage(uiLogic()->m_selectedServerIndex, - uiLogic()->m_selectedDockerContainer); - emit uiLogic()->goToPage(Page::ShareConnection); -} diff --git a/client/ui/pages_logic/GeneralSettingsLogic.h b/client/ui/pages_logic/GeneralSettingsLogic.h deleted file mode 100644 index a0cff333..00000000 --- a/client/ui/pages_logic/GeneralSettingsLogic.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef GENERAL_SETTINGS_LOGIC_H -#define GENERAL_SETTINGS_LOGIC_H - -#include "PageLogicBase.h" - -class UiLogic; - -class GeneralSettingsLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(bool, pushButtonGeneralSettingsShareConnectionEnable) - AUTO_PROPERTY(bool, existsAnyServer) - -public: - Q_INVOKABLE void onUpdatePage() override; - Q_INVOKABLE void onPushButtonGeneralSettingsServerSettingsClicked(); - Q_INVOKABLE void onPushButtonGeneralSettingsShareConnectionClicked(); - -public: - explicit GeneralSettingsLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~GeneralSettingsLogic() = default; - -}; -#endif // GENERAL_SETTINGS_LOGIC_H diff --git a/client/ui/pages_logic/NetworkSettingsLogic.cpp b/client/ui/pages_logic/NetworkSettingsLogic.cpp deleted file mode 100644 index 1315aa10..00000000 --- a/client/ui/pages_logic/NetworkSettingsLogic.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include "NetworkSettingsLogic.h" - -#include "version.h" -#include "utilities.h" -#include "settings.h" - -NetworkSettingsLogic::NetworkSettingsLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent), - m_checkBoxUseAmneziaDnsChecked{false}, - m_ipAddressRegex{Utils::ipAddressRegExp()} -{ - -} - -void NetworkSettingsLogic::onUpdatePage() -{ - set_checkBoxUseAmneziaDnsChecked(m_settings->useAmneziaDns()); - - set_lineEditDns1Text(m_settings->primaryDns()); - set_lineEditDns2Text(m_settings->secondaryDns()); -} - -void NetworkSettingsLogic::onLineEditDns1EditFinished(const QString &text) -{ - if (ipAddressRegex().match(text).hasMatch()) { - m_settings->setPrimaryDns(text); - } -} - -void NetworkSettingsLogic::onLineEditDns2EditFinished(const QString &text) -{ - if (ipAddressRegex().match(text).hasMatch()) { - m_settings->setSecondaryDns(text); - } -} - -void NetworkSettingsLogic::onPushButtonResetDns1Clicked() -{ - m_settings->setPrimaryDns(m_settings->cloudFlareNs1); - onUpdatePage(); -} - -void NetworkSettingsLogic::onPushButtonResetDns2Clicked() -{ - m_settings->setSecondaryDns(m_settings->cloudFlareNs2); - onUpdatePage(); -} - -void NetworkSettingsLogic::onCheckBoxUseAmneziaDnsToggled(bool checked) -{ - m_settings->setUseAmneziaDns(checked); -} diff --git a/client/ui/pages_logic/NetworkSettingsLogic.h b/client/ui/pages_logic/NetworkSettingsLogic.h deleted file mode 100644 index 237706eb..00000000 --- a/client/ui/pages_logic/NetworkSettingsLogic.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef NETWORK_SETTINGS_LOGIC_H -#define NETWORK_SETTINGS_LOGIC_H - -#include "PageLogicBase.h" - -#include - -class UiLogic; - -class NetworkSettingsLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(bool, checkBoxUseAmneziaDnsChecked) - - AUTO_PROPERTY(QString, lineEditDns1Text) - AUTO_PROPERTY(QString, lineEditDns2Text) - READONLY_PROPERTY(QRegularExpression, ipAddressRegex) - -public: - Q_INVOKABLE void onUpdatePage() override; - - Q_INVOKABLE void onLineEditDns1EditFinished(const QString& text); - Q_INVOKABLE void onLineEditDns2EditFinished(const QString& text); - Q_INVOKABLE void onPushButtonResetDns1Clicked(); - Q_INVOKABLE void onPushButtonResetDns2Clicked(); - - Q_INVOKABLE void onCheckBoxUseAmneziaDnsToggled(bool checked); - -public: - explicit NetworkSettingsLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~NetworkSettingsLogic() = default; - -}; -#endif // NETWORK_SETTINGS_LOGIC_H diff --git a/client/ui/pages_logic/NewServerProtocolsLogic.cpp b/client/ui/pages_logic/NewServerProtocolsLogic.cpp deleted file mode 100644 index a1db7565..00000000 --- a/client/ui/pages_logic/NewServerProtocolsLogic.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "NewServerProtocolsLogic.h" -#include "../uilogic.h" - -NewServerProtocolsLogic::NewServerProtocolsLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent), - m_progressBarConnectionMinimum{0}, - m_progressBarConnectionMaximum{100} -{ -} - - -void NewServerProtocolsLogic::onUpdatePage() -{ - set_progressBarConnectionMinimum(0); - set_progressBarConnectionMaximum(300); -} - -void NewServerProtocolsLogic::onPushButtonConfigureClicked(DockerContainer c, int port, TransportProto tp) -{ - Proto mainProto = ContainerProps::defaultProtocol(c); - - QJsonObject config { - { config_key::container, ContainerProps::containerToString(c) }, - { ProtocolProps::protoToString(mainProto), QJsonObject { - { config_key::port, QString::number(port) }, - { config_key::transport_proto, ProtocolProps::transportProtoToString(tp, mainProto) }} - } - }; - - QPair container(c, config); - - uiLogic()->installServer(container); -} - diff --git a/client/ui/pages_logic/NewServerProtocolsLogic.h b/client/ui/pages_logic/NewServerProtocolsLogic.h deleted file mode 100644 index abf3d102..00000000 --- a/client/ui/pages_logic/NewServerProtocolsLogic.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef NEW_SERVER_PROTOCOLS_LOGIC_H -#define NEW_SERVER_PROTOCOLS_LOGIC_H - -#include "PageLogicBase.h" -#include "containers/containers_defs.h" - -class UiLogic; - -class NewServerProtocolsLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(double, progressBarConnectionMinimum) - AUTO_PROPERTY(double, progressBarConnectionMaximum) - -public: - Q_INVOKABLE void onUpdatePage() override; - Q_INVOKABLE void onPushButtonConfigureClicked(DockerContainer c, int port, TransportProto tp); - -public: - explicit NewServerProtocolsLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~NewServerProtocolsLogic() = default; - -}; -#endif // NEW_SERVER_PROTOCOLS_LOGIC_H diff --git a/client/ui/pages_logic/PageLogicBase.cpp b/client/ui/pages_logic/PageLogicBase.cpp deleted file mode 100644 index 05c4e3a1..00000000 --- a/client/ui/pages_logic/PageLogicBase.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include "PageLogicBase.h" - -#include "ui/uilogic.h" -#include "settings.h" -#include "configurators/vpn_configurator.h" - -PageLogicBase::PageLogicBase(UiLogic *logic, QObject *parent): - QObject(parent), - m_pageEnabled{true}, - m_uiLogic(logic) -{ - m_settings = logic->m_settings; - m_configurator = logic->m_configurator; -} - - diff --git a/client/ui/pages_logic/PageLogicBase.h b/client/ui/pages_logic/PageLogicBase.h deleted file mode 100644 index 6616de9d..00000000 --- a/client/ui/pages_logic/PageLogicBase.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef PAGE_LOGIC_BASE_H -#define PAGE_LOGIC_BASE_H - -#include "../pages.h" -#include "../property_helper.h" - -using namespace PageEnumNS; - -class UiLogic; -class Settings; -class VpnConfigurator; -class ServerController; - -class PageLogicBase : public QObject -{ - Q_OBJECT - AUTO_PROPERTY(bool, pageEnabled) - -public: - explicit PageLogicBase(UiLogic *uiLogic, QObject *parent = nullptr); - ~PageLogicBase() = default; - - Q_INVOKABLE virtual void onUpdatePage() {} - -protected: - UiLogic *m_uiLogic; - UiLogic *uiLogic() const { return m_uiLogic; } - - std::shared_ptr m_settings; - std::shared_ptr m_configurator; - -signals: - void updatePage(); -}; -#endif // PAGE_LOGIC_BASE_H diff --git a/client/ui/pages_logic/QrDecoderLogic.cpp b/client/ui/pages_logic/QrDecoderLogic.cpp deleted file mode 100644 index e1845c77..00000000 --- a/client/ui/pages_logic/QrDecoderLogic.cpp +++ /dev/null @@ -1,125 +0,0 @@ -#include "QrDecoderLogic.h" - -#include "ui/uilogic.h" -#include "ui/pages_logic/StartPageLogic.h" - -#ifdef Q_OS_ANDROID -#include -#include -#include "../../platforms/android/androidutils.h" -#endif - -using namespace amnezia; -using namespace PageEnumNS; - -namespace { - QrDecoderLogic* mInstance = nullptr; - constexpr auto CLASSNAME = "org.amnezia.vpn.qt.CameraActivity"; -} - -QrDecoderLogic::QrDecoderLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent) -{ - mInstance = this; - - #if (defined(Q_OS_ANDROID)) - AndroidUtils::runOnAndroidThreadAsync([]() { - JNINativeMethod methods[]{ - {"passDataToDecoder", "(Ljava/lang/String;)V", reinterpret_cast(onNewDataChunk)}, - }; - - QJniObject javaClass(CLASSNAME); - QJniEnvironment env; - jclass objectClass = env->GetObjectClass(javaClass.object()); - env->RegisterNatives(objectClass, methods, sizeof(methods) / sizeof(methods[0])); - env->DeleteLocalRef(objectClass); - }); - #endif -} - -void QrDecoderLogic::stopDecodingQr() -{ - #if (defined(Q_OS_ANDROID)) - QJniObject::callStaticMethod(CLASSNAME, "stopQrCodeReader", "()V"); - #endif - - emit stopDecode(); -} - -#ifdef Q_OS_ANDROID -void QrDecoderLogic::onNewDataChunk(JNIEnv *env, jobject thiz, jstring data) -{ - Q_UNUSED(thiz); - const char* buffer = env->GetStringUTFChars(data, nullptr); - if (!buffer) { - return; - } - - QString parcelBody(buffer); - env->ReleaseStringUTFChars(data, buffer); - - if (mInstance != nullptr) { - if (!mInstance->m_detectingEnabled) { - mInstance->onUpdatePage(); - } - mInstance->onDetectedQrCode(parcelBody); - } -} -#endif - -void QrDecoderLogic::onUpdatePage() -{ - m_chunks.clear(); - set_detectingEnabled(true); - set_totalChunksCount(0); - set_receivedChunksCount(0); - emit startDecode(); -} - -void QrDecoderLogic::onDetectedQrCode(const QString &code) -{ - //qDebug() << code; - if (!detectingEnabled()) return; - - // check if chunk received - QByteArray ba = QByteArray::fromBase64(code.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); - QDataStream s(&ba, QIODevice::ReadOnly); - qint16 magic; s >> magic; - - if (magic == amnezia::qrMagicCode) { - quint8 chunksCount; s >> chunksCount; - if (totalChunksCount() != chunksCount) { - m_chunks.clear(); - } - - set_totalChunksCount(chunksCount); - - quint8 chunkId; s >> chunkId; - s >> m_chunks[chunkId]; - set_receivedChunksCount(m_chunks.size()); - - if (m_chunks.size() == totalChunksCount()) { - QByteArray data; - - for (int i = 0; i < totalChunksCount(); ++i) { - data.append(m_chunks.value(i)); - } - - bool ok = uiLogic()->pageLogic()->importConnectionFromQr(data); - if (ok) { - set_detectingEnabled(false); - stopDecodingQr(); - } else { - m_chunks.clear(); - set_totalChunksCount(0); - set_receivedChunksCount(0); - } - } - } else { - bool ok = uiLogic()->pageLogic()->importConnectionFromQr(ba); - if (ok) { - set_detectingEnabled(false); - stopDecodingQr(); - } - } -} diff --git a/client/ui/pages_logic/QrDecoderLogic.h b/client/ui/pages_logic/QrDecoderLogic.h deleted file mode 100644 index 2b24bf27..00000000 --- a/client/ui/pages_logic/QrDecoderLogic.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef QR_DECODER_LOGIC_H -#define QR_DECODER_LOGIC_H - -#include "PageLogicBase.h" - -#ifdef Q_OS_ANDROID -#include "jni.h" -#endif - -class UiLogic; - -class QrDecoderLogic : public PageLogicBase -{ - Q_OBJECT - AUTO_PROPERTY(bool, detectingEnabled) - AUTO_PROPERTY(int, totalChunksCount) - AUTO_PROPERTY(int, receivedChunksCount) - -public: - Q_INVOKABLE void onUpdatePage() override; - Q_INVOKABLE void onDetectedQrCode(const QString &code); - -#ifdef Q_OS_ANDROID - static void onNewDataChunk(JNIEnv *env, jobject thiz, jstring data); -#endif - -public: - explicit QrDecoderLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~QrDecoderLogic() = default; - -private: - void stopDecodingQr(); - -signals: - void startDecode(); - void stopDecode(); - -private: - QMap m_chunks; -}; -#endif // QR_DECODER_LOGIC_H diff --git a/client/ui/pages_logic/ServerConfiguringProgressLogic.cpp b/client/ui/pages_logic/ServerConfiguringProgressLogic.cpp deleted file mode 100644 index 2b42fac9..00000000 --- a/client/ui/pages_logic/ServerConfiguringProgressLogic.cpp +++ /dev/null @@ -1,187 +0,0 @@ -#include "ServerConfiguringProgressLogic.h" -#include "version.h" -#include "core/errorstrings.h" -#include -#include - -#include "core/servercontroller.h" - -ServerConfiguringProgressLogic::ServerConfiguringProgressLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent), - m_progressBarValue{0}, - m_labelWaitInfoVisible{true}, - m_labelWaitInfoText{tr("Please wait, configuring process may take up to 5 minutes")}, - m_progressBarVisible{true}, - m_progressBarMaximum{100}, - m_progressBarTextVisible{true}, - m_progressBarText{tr("Configuring...")}, - m_labelServerBusyVisible{false}, - m_labelServerBusyText{""} -{ - -} - -void ServerConfiguringProgressLogic::onUpdatePage() -{ - set_progressBarValue(0); -} - - -ErrorCode ServerConfiguringProgressLogic::doInstallAction(const std::function &action) -{ - PageFunc page; - page.setEnabledFunc = [this] (bool enabled) -> void { - set_pageEnabled(enabled); - }; - ButtonFunc noButton; - LabelFunc noWaitInfo; - ProgressFunc progress; - progress.setVisibleFunc = [this] (bool visible) -> void { - set_progressBarVisible(visible); - }; - - progress.setValueFunc = [this] (int value) -> void { - set_progressBarValue(value); - }; - progress.getValueFunc = [this] (void) -> int { - return progressBarValue(); - }; - progress.getMaximumFunc = [this] (void) -> int { - return progressBarMaximum(); - }; - - LabelFunc busyInfo; - busyInfo.setTextFunc = [this] (const QString& text) -> void { - set_labelServerBusyText(text); - }; - busyInfo.setVisibleFunc = [this] (bool visible) -> void { - set_labelServerBusyVisible(visible); - }; - - ButtonFunc cancelButton; - cancelButton.setVisibleFunc = [this] (bool visible) -> void { - set_pushButtonCancelVisible(visible); - }; - - return doInstallAction(action, page, progress, noButton, noWaitInfo, busyInfo, cancelButton); -} - -ErrorCode ServerConfiguringProgressLogic::doInstallAction(const std::function &action, - const PageFunc &page, - const ProgressFunc &progress, - const ButtonFunc &saveButton, - const LabelFunc &waitInfo, - const LabelFunc &serverBusyInfo, - const ButtonFunc &cancelButton) -{ - progress.setVisibleFunc(true); - if (page.setEnabledFunc) { - page.setEnabledFunc(false); - } - if (saveButton.setVisibleFunc) { - saveButton.setVisibleFunc(false); - } - if (waitInfo.setVisibleFunc) { - waitInfo.setVisibleFunc(true); - } - if (waitInfo.setTextFunc) { - waitInfo.setTextFunc(tr("Please wait, configuring process may take up to 5 minutes")); - } - - QTimer timer; - connect(&timer, &QTimer::timeout, [progress](){ - progress.setValueFunc(progress.getValueFunc() + 1); - }); - - progress.setValueFunc(0); - timer.start(1000); - - ServerController serverController(m_settings); - - QMetaObject::Connection cancelDoInstallActionConnection; - if (cancelButton.setVisibleFunc) { - cancelDoInstallActionConnection = connect(this, &ServerConfiguringProgressLogic::cancelDoInstallAction, - &serverController, &ServerController::setCancelInstallation); - } - - - QMetaObject::Connection serverBusyConnection; - if (serverBusyInfo.setVisibleFunc && serverBusyInfo.setTextFunc) { - auto onServerIsBusy = [&serverBusyInfo, &timer, &cancelButton](const bool isBusy) { - isBusy ? timer.stop() : timer.start(1000); - serverBusyInfo.setVisibleFunc(isBusy); - serverBusyInfo.setTextFunc(isBusy ? "Amnesia has detected that your server is currently " - "busy installing other software. Amnesia installation " - "will pause until the server finishes installing other software" - : ""); - if (cancelButton.setVisibleFunc) { - cancelButton.setVisibleFunc(isBusy ? true : false); - } - }; - - serverBusyConnection = connect(&serverController, &ServerController::serverIsBusy, this, onServerIsBusy); - } - - ErrorCode e = action(); - qDebug() << "doInstallAction finished with code" << e; - if (cancelButton.setVisibleFunc) { - disconnect(cancelDoInstallActionConnection); - } - - if (serverBusyInfo.setVisibleFunc && serverBusyInfo.setTextFunc) { - disconnect(serverBusyConnection); - } - - if (e) { - if (page.setEnabledFunc) { - page.setEnabledFunc(true); - } - if (saveButton.setVisibleFunc) { - saveButton.setVisibleFunc(true); - } - if (waitInfo.setVisibleFunc) { - waitInfo.setVisibleFunc(false); - } - - progress.setVisibleFunc(false); - return e; - } - - // just ui progressbar tweak - timer.stop(); - - int remainingVal = progress.getMaximumFunc() - progress.getValueFunc(); - - if (remainingVal > 0) { - QTimer timer1; - QEventLoop loop1; - - connect(&timer1, &QTimer::timeout, [&](){ - progress.setValueFunc(progress.getValueFunc() + 1); - if (progress.getValueFunc() >= progress.getMaximumFunc()) { - loop1.quit(); - } - }); - - timer1.start(5); - loop1.exec(); - } - - - progress.setVisibleFunc(false); - if (saveButton.setVisibleFunc) { - saveButton.setVisibleFunc(true); - } - if (page.setEnabledFunc) { - page.setEnabledFunc(true); - } - if (waitInfo.setTextFunc) { - waitInfo.setTextFunc(tr("Operation finished")); - } - return ErrorCode::NoError; -} - -void ServerConfiguringProgressLogic::onPushButtonCancelClicked() -{ - emit cancelDoInstallAction(true); -} diff --git a/client/ui/pages_logic/ServerConfiguringProgressLogic.h b/client/ui/pages_logic/ServerConfiguringProgressLogic.h deleted file mode 100644 index 9f10bc87..00000000 --- a/client/ui/pages_logic/ServerConfiguringProgressLogic.h +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef SERVER_CONFIGURING_PROGRESS_LOGIC_H -#define SERVER_CONFIGURING_PROGRESS_LOGIC_H - -#include -#include "PageLogicBase.h" -#include "core/defs.h" - -using namespace amnezia; - -class UiLogic; - -class ServerConfiguringProgressLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(double, progressBarValue) - AUTO_PROPERTY(bool, labelWaitInfoVisible) - AUTO_PROPERTY(QString, labelWaitInfoText) - AUTO_PROPERTY(bool, progressBarVisible) - AUTO_PROPERTY(int, progressBarMaximum) - AUTO_PROPERTY(bool, progressBarTextVisible) - AUTO_PROPERTY(QString, progressBarText) - AUTO_PROPERTY(bool, labelServerBusyVisible) - AUTO_PROPERTY(QString, labelServerBusyText) - AUTO_PROPERTY(bool, pushButtonCancelVisible) - -public: - Q_INVOKABLE void onPushButtonCancelClicked(); - -private: - struct ProgressFunc { - std::function setVisibleFunc; - std::function setValueFunc; - std::function getValueFunc; - std::function getMaximumFunc; - std::function setTextVisibleFunc; - std::function setTextFunc; - }; - struct PageFunc { - std::function setEnabledFunc; - }; - struct ButtonFunc { - std::function setVisibleFunc; - }; - struct LabelFunc { - std::function setVisibleFunc; - std::function setTextFunc; - }; - -public: - explicit ServerConfiguringProgressLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~ServerConfiguringProgressLogic() = default; - - friend class OpenVpnLogic; - friend class ShadowSocksLogic; - friend class CloakLogic; - friend class UiLogic; - - void onUpdatePage() override; - ErrorCode doInstallAction(const std::function &action); - ErrorCode doInstallAction(const std::function &action, - const PageFunc &page, - const ProgressFunc &progress, - const ButtonFunc &saveButton, - const LabelFunc &waitInfo, - const LabelFunc &serverBusyInfo, - const ButtonFunc &cancelButton); - -signals: - void cancelDoInstallAction(const bool cancel); - -}; -#endif // SERVER_CONFIGURING_PROGRESS_LOGIC_H diff --git a/client/ui/pages_logic/ServerContainersLogic.cpp b/client/ui/pages_logic/ServerContainersLogic.cpp deleted file mode 100644 index 89eeb6c8..00000000 --- a/client/ui/pages_logic/ServerContainersLogic.cpp +++ /dev/null @@ -1,128 +0,0 @@ -#include "ServerContainersLogic.h" -#include "ServerConfiguringProgressLogic.h" -#include "ShareConnectionLogic.h" - -#include - -#include "protocols/PageProtocolLogicBase.h" - -#include "core/servercontroller.h" -#include - -#include "../pages_logic/VpnLogic.h" -#include "../uilogic.h" -#include "core/errorstrings.h" -#include "vpnconnection.h" - -ServerContainersLogic::ServerContainersLogic(UiLogic *logic, QObject *parent) : PageLogicBase(logic, parent) -{ -} - -void ServerContainersLogic::onUpdatePage() -{ - // ContainersModel *c_model = qobject_cast(uiLogic()->containersModel()); - // c_model->setSelectedServerIndex(uiLogic()->m_selectedServerIndex); - - ProtocolsModel *p_model = qobject_cast(uiLogic()->protocolsModel()); - // p_model->setSelectedServerIndex(uiLogic()->m_selectedServerIndex); - - set_isManagedServer(m_settings->haveAuthData(uiLogic()->m_selectedServerIndex)); - uiLogic()->m_installCredentials = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex); - emit updatePage(); -} - -void ServerContainersLogic::onPushButtonProtoSettingsClicked(DockerContainer c, Proto p) -{ - qDebug() << "ServerContainersLogic::onPushButtonProtoSettingsClicked" << c << p; - uiLogic()->m_selectedDockerContainer = c; - uiLogic()->protocolLogic(p)->updateProtocolPage( - m_settings->protocolConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer, p), - uiLogic()->m_selectedDockerContainer, m_settings->haveAuthData(uiLogic()->m_selectedServerIndex)); - - emit uiLogic()->goToProtocolPage(p); -} - -void ServerContainersLogic::onPushButtonDefaultClicked(DockerContainer c) -{ - if (m_settings->defaultContainer(uiLogic()->m_selectedServerIndex) == c) - return; - - m_settings->setDefaultContainer(uiLogic()->m_selectedServerIndex, c); - uiLogic()->onUpdateAllPages(); - - if (uiLogic()->m_selectedServerIndex != m_settings->defaultServerIndex()) - return; - if (!uiLogic()->m_vpnConnection) - return; - if (!uiLogic()->m_vpnConnection->isConnected()) - return; - - uiLogic()->pageLogic()->onDisconnect(); - uiLogic()->pageLogic()->onConnect(); -} - -void ServerContainersLogic::onPushButtonShareClicked(DockerContainer c) -{ - uiLogic()->pageLogic()->updateSharingPage(uiLogic()->m_selectedServerIndex, c); - emit uiLogic()->goToPage(Page::ShareConnection); -} - -void ServerContainersLogic::onPushButtonRemoveClicked(DockerContainer container) -{ - // buttonSetEnabledFunc(false); - ServerController serverController(m_settings); - ErrorCode e = - serverController.removeContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), container); - m_settings->removeContainerConfig(uiLogic()->m_selectedServerIndex, container); - // buttonSetEnabledFunc(true); - - if (m_settings->defaultContainer(uiLogic()->m_selectedServerIndex) == container) { - const auto &c = m_settings->containers(uiLogic()->m_selectedServerIndex); - if (c.isEmpty()) - m_settings->setDefaultContainer(uiLogic()->m_selectedServerIndex, DockerContainer::None); - else - m_settings->setDefaultContainer(uiLogic()->m_selectedServerIndex, c.keys().first()); - } - uiLogic()->onUpdateAllPages(); -} - -void ServerContainersLogic::onPushButtonContinueClicked(DockerContainer c, int port, TransportProto tp) -{ - ServerController serverController(m_settings); - QJsonObject config; // = serverController.createContainerInitialConfig(c, port, tp); - - emit uiLogic()->goToPage(Page::ServerConfiguringProgress); - qApp->processEvents(); - - bool isServerCreated = false; - ErrorCode errorCode = uiLogic()->addAlreadyInstalledContainersGui(isServerCreated); - - if (errorCode == ErrorCode::NoError) { - if (!uiLogic()->isContainerAlreadyAddedToGui(c)) { - auto installAction = [this, c, &config]() { - ServerController serverController(m_settings); - return serverController.setupContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), - c, config); - }; - errorCode = uiLogic()->pageLogic()->doInstallAction(installAction); - - if (errorCode == ErrorCode::NoError) { - m_settings->setContainerConfig(uiLogic()->m_selectedServerIndex, c, config); - if (ContainerProps::containerService(c) == ServiceType::Vpn) { - m_settings->setDefaultContainer(uiLogic()->m_selectedServerIndex, c); - } - } - } else { - emit uiLogic()->showWarningMessage( - "Attention! The container you are trying to install is already installed on the server. " - "All installed containers have been added to the application "); - } - - uiLogic()->onUpdateAllPages(); - } - if (errorCode != ErrorCode::NoError) { - emit uiLogic()->showWarningMessage(tr("Error occurred while configuring server.") + "\n" + tr("Error message: ") - + errorString(errorCode) + "\n" + tr("See logs for details.")); - } - emit uiLogic()->closePage(); -} diff --git a/client/ui/pages_logic/ServerContainersLogic.h b/client/ui/pages_logic/ServerContainersLogic.h deleted file mode 100644 index d0081516..00000000 --- a/client/ui/pages_logic/ServerContainersLogic.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef SERVER_CONTAINERS_LOGIC_H -#define SERVER_CONTAINERS_LOGIC_H - -#include "PageLogicBase.h" -#include "containers/containers_defs.h" - -class UiLogic; - -class ServerContainersLogic : public PageLogicBase -{ - Q_OBJECT - -public: - Q_INVOKABLE void onUpdatePage() override; - - Q_INVOKABLE void onPushButtonProtoSettingsClicked(DockerContainer c, Proto p); - Q_INVOKABLE void onPushButtonDefaultClicked(DockerContainer c); - Q_INVOKABLE void onPushButtonShareClicked(DockerContainer c); - Q_INVOKABLE void onPushButtonRemoveClicked(DockerContainer c); - Q_INVOKABLE void onPushButtonContinueClicked(DockerContainer c, int port, TransportProto tp); - - AUTO_PROPERTY(bool, isManagedServer) - -public: - explicit ServerContainersLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~ServerContainersLogic() = default; - -}; -#endif // SERVER_CONTAINERS_LOGIC_H diff --git a/client/ui/pages_logic/ServerListLogic.cpp b/client/ui/pages_logic/ServerListLogic.cpp deleted file mode 100644 index 16775bc0..00000000 --- a/client/ui/pages_logic/ServerListLogic.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "ServerListLogic.h" - -#include "../models/servers_model.h" -#include "../uilogic.h" -#include "vpnconnection.h" - -ServerListLogic::ServerListLogic(UiLogic *logic, QObject *parent) - : PageLogicBase(logic, parent), m_serverListModel { new ServersModel(m_settings, this) } -{ -} - -void ServerListLogic::onServerListPushbuttonDefaultClicked(int index) -{ - m_settings->setDefaultServer(index); - uiLogic()->onUpdateAllPages(); - emit currServerIdxChanged(); -} - -void ServerListLogic::onServerListPushbuttonSettingsClicked(int index) -{ - uiLogic()->m_selectedServerIndex = index; - uiLogic()->goToPage(Page::ServerSettings); -} - -int ServerListLogic::currServerIdx() const -{ - return m_settings->defaultServerIndex(); -} - -void ServerListLogic::onUpdatePage() -{ - // const QJsonArray &servers = m_settings->serversArray(); - // int defaultServer = m_settings->defaultServerIndex(); - // QVector serverListContent; - // for(int i = 0; i < servers.size(); i++) { - // ServerModelContent c; - // auto server = servers.at(i).toObject(); - // c.desc = server.value(config_key::description).toString(); - // c.address = server.value(config_key::hostName).toString(); - // if (c.desc.isEmpty()) { - // c.desc = c.address; - // } - // c.isDefault = (i == defaultServer); - // serverListContent.push_back(c); - // } - // qobject_cast(m_serverListModel)->setContent(serverListContent); -} diff --git a/client/ui/pages_logic/ServerListLogic.h b/client/ui/pages_logic/ServerListLogic.h deleted file mode 100644 index b4f47547..00000000 --- a/client/ui/pages_logic/ServerListLogic.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef SERVER_LIST_LOGIC_H -#define SERVER_LIST_LOGIC_H - -#include "PageLogicBase.h" - -class UiLogic; - -class ServerListLogic : public PageLogicBase -{ - Q_OBJECT - - READONLY_PROPERTY(QObject *, serverListModel) - Q_PROPERTY(int currServerIdx READ currServerIdx NOTIFY currServerIdxChanged) - -public: - int currServerIdx() const; - - Q_INVOKABLE void onUpdatePage() override; - Q_INVOKABLE void onServerListPushbuttonDefaultClicked(int index); - Q_INVOKABLE void onServerListPushbuttonSettingsClicked(int index); - -public: - explicit ServerListLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~ServerListLogic() = default; - -signals: - void currServerIdxChanged(); - -}; -#endif // SERVER_LIST_LOGIC_H diff --git a/client/ui/pages_logic/ServerSettingsLogic.cpp b/client/ui/pages_logic/ServerSettingsLogic.cpp deleted file mode 100644 index 4c68b549..00000000 --- a/client/ui/pages_logic/ServerSettingsLogic.cpp +++ /dev/null @@ -1,144 +0,0 @@ -#include "ServerSettingsLogic.h" -#include "vpnconnection.h" - -#include "../uilogic.h" -#include "ShareConnectionLogic.h" -#include "VpnLogic.h" - -#include "core/errorstrings.h" -#include -#include - -#if defined(Q_OS_ANDROID) - #include "../../platforms/android/androidutils.h" -#endif - -ServerSettingsLogic::ServerSettingsLogic(UiLogic *logic, QObject *parent) - : PageLogicBase(logic, parent), - m_labelWaitInfoVisible { true }, - m_pushButtonClearClientCacheVisible { true }, - m_pushButtonShareFullVisible { true }, - m_pushButtonClearClientCacheText { tr("Clear client cached profile") } -{ -} - -void ServerSettingsLogic::onUpdatePage() -{ - set_labelWaitInfoVisible(false); - set_labelWaitInfoText(""); - set_pushButtonClearClientCacheVisible(m_settings->haveAuthData(uiLogic()->m_selectedServerIndex)); - set_pushButtonShareFullVisible(m_settings->haveAuthData(uiLogic()->m_selectedServerIndex)); - const QJsonObject &server = m_settings->server(uiLogic()->m_selectedServerIndex); - const QString &port = server.value(config_key::port).toString(); - - const QString &userName = server.value(config_key::userName).toString(); - const QString &hostName = server.value(config_key::hostName).toString(); - QString name = QString("%1%2%3%4%5") - .arg(userName) - .arg(userName.isEmpty() ? "" : "@") - .arg(hostName) - .arg(port.isEmpty() ? "" : ":") - .arg(port); - - set_labelServerText(name); - set_lineEditDescriptionText(server.value(config_key::description).toString()); - - DockerContainer selectedContainer = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex); - QString selectedContainerName = ContainerProps::containerHumanNames().value(selectedContainer); - set_labelCurrentVpnProtocolText(tr("Service: ") + selectedContainerName); -} - -void ServerSettingsLogic::onPushButtonForgetServer() -{ - if (m_settings->defaultServerIndex() == uiLogic()->m_selectedServerIndex - && uiLogic()->m_vpnConnection->isConnected()) { - uiLogic()->pageLogic()->onDisconnect(); - } - m_settings->removeServer(uiLogic()->m_selectedServerIndex); - - if (m_settings->defaultServerIndex() == uiLogic()->m_selectedServerIndex) { - m_settings->setDefaultServer(0); - } else if (m_settings->defaultServerIndex() > uiLogic()->m_selectedServerIndex) { - m_settings->setDefaultServer(m_settings->defaultServerIndex() - 1); - } - - if (m_settings->serversCount() == 0) { - m_settings->setDefaultServer(-1); - } - - uiLogic()->m_selectedServerIndex = -1; - uiLogic()->onUpdateAllPages(); - - if (m_settings->serversCount() == 0) { - uiLogic()->setStartPage(Page::Start); - } else { - uiLogic()->closePage(); - } -} - -void ServerSettingsLogic::onPushButtonClearClientCacheClicked() -{ - set_pushButtonClearClientCacheText(tr("Cache cleared")); - - const auto &containers = m_settings->containers(uiLogic()->m_selectedServerIndex); - for (DockerContainer container : containers.keys()) { - m_settings->clearLastConnectionConfig(uiLogic()->m_selectedServerIndex, container); - } - - QTimer::singleShot(3000, this, [this]() { set_pushButtonClearClientCacheText(tr("Clear client cached profile")); }); -} - -void ServerSettingsLogic::onLineEditDescriptionEditingFinished() -{ - const QString &newText = lineEditDescriptionText(); - QJsonObject server = m_settings->server(uiLogic()->m_selectedServerIndex); - server.insert(config_key::description, newText); - m_settings->editServer(uiLogic()->m_selectedServerIndex, server); - uiLogic()->onUpdateAllPages(); -} - -bool ServerSettingsLogic::isCurrentServerHasCredentials() -{ - return m_settings->haveAuthData(uiLogic()->m_selectedServerIndex); -} - -#if defined(Q_OS_ANDROID) -/* Auth result handler for Android */ -void authResultReceiver::handleActivityResult(int receiverRequestCode, int resultCode, const QJniObject &data) -{ - qDebug() << "receiverRequestCode" << receiverRequestCode << "resultCode" << resultCode; - - if (resultCode == -1) { // ResultOK - uiLogic()->pageLogic()->updateSharingPage(m_serverIndex, DockerContainer::None); - emit uiLogic()->goToShareProtocolPage(Proto::Any); - } -} -#endif - -void ServerSettingsLogic::onPushButtonShareFullClicked() -{ -#if defined(Q_OS_ANDROID) - /* We use builtin keyguard for ssh key export protection on Android */ - QJniObject activity = AndroidUtils::getActivity(); - auto appContext = activity.callObjectMethod("getApplicationContext", "()Landroid/content/Context;"); - if (appContext.isValid()) { - QAndroidActivityResultReceiver *receiver = new authResultReceiver(uiLogic(), uiLogic()->m_selectedServerIndex); - auto intent = QJniObject::callStaticObjectMethod("org/amnezia/vpn/AuthHelper", "getAuthIntent", - "(Landroid/content/Context;)Landroid/content/Intent;", - appContext.object()); - if (intent.isValid()) { - if (intent.object() != nullptr) { - QtAndroidPrivate::startActivity(intent.object(), 1, receiver); - } - } else { - uiLogic()->pageLogic()->updateSharingPage(uiLogic()->m_selectedServerIndex, - DockerContainer::None); - emit uiLogic()->goToShareProtocolPage(Proto::Any); - } - } -#else - uiLogic()->pageLogic()->updateSharingPage(uiLogic()->m_selectedServerIndex, - DockerContainer::None); - emit uiLogic()->goToShareProtocolPage(Proto::Any); -#endif -} diff --git a/client/ui/pages_logic/ServerSettingsLogic.h b/client/ui/pages_logic/ServerSettingsLogic.h deleted file mode 100644 index 3ce26164..00000000 --- a/client/ui/pages_logic/ServerSettingsLogic.h +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef SERVER_SETTINGS_LOGIC_H -#define SERVER_SETTINGS_LOGIC_H - -#include "PageLogicBase.h" - -#if defined(Q_OS_ANDROID) -#include -#include -#endif - -class UiLogic; - -class ServerSettingsLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(bool, labelWaitInfoVisible) - AUTO_PROPERTY(QString, labelWaitInfoText) - AUTO_PROPERTY(QString, pushButtonClearClientCacheText) - AUTO_PROPERTY(bool, pushButtonClearClientCacheVisible) - AUTO_PROPERTY(bool, pushButtonShareFullVisible) - AUTO_PROPERTY(QString, labelServerText) - AUTO_PROPERTY(QString, lineEditDescriptionText) - AUTO_PROPERTY(QString, labelCurrentVpnProtocolText) - -public: - Q_INVOKABLE void onUpdatePage() override; - - Q_INVOKABLE void onPushButtonForgetServer(); - Q_INVOKABLE void onPushButtonShareFullClicked(); - Q_INVOKABLE void onPushButtonClearClientCacheClicked(); - Q_INVOKABLE void onLineEditDescriptionEditingFinished(); - - Q_INVOKABLE bool isCurrentServerHasCredentials(); - -public: - explicit ServerSettingsLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~ServerSettingsLogic() = default; - -}; - -#if defined(Q_OS_ANDROID) -/* Auth result handler for Android */ -class authResultReceiver final : public PageLogicBase, public QAndroidActivityResultReceiver -{ -Q_OBJECT - -public: - authResultReceiver(UiLogic *uiLogic, int serverIndex , QObject *parent = nullptr) : PageLogicBase(uiLogic, parent) { - m_serverIndex = serverIndex; - } - ~authResultReceiver() {} - -public: - void handleActivityResult(int receiverRequestCode, int resultCode, const QJniObject &data) override; - -private: - int m_serverIndex; -}; -#endif - -#endif // SERVER_SETTINGS_LOGIC_H diff --git a/client/ui/pages_logic/ShareConnectionLogic.cpp b/client/ui/pages_logic/ShareConnectionLogic.cpp deleted file mode 100644 index 911c872c..00000000 --- a/client/ui/pages_logic/ShareConnectionLogic.cpp +++ /dev/null @@ -1,293 +0,0 @@ -#include -#include -#include - -#include "qrcodegen.hpp" - -#include "ShareConnectionLogic.h" - -#include "configurators/cloak_configurator.h" -#include "configurators/vpn_configurator.h" -#include "configurators/openvpn_configurator.h" -#include "configurators/shadowsocks_configurator.h" -#include "configurators/wireguard_configurator.h" -#include "configurators/ikev2_configurator.h" -#include "configurators/ssh_configurator.h" - -#include "version.h" -#include "core/defs.h" -#include "core/errorstrings.h" -#include "core/servercontroller.h" -#include - -#include "../uilogic.h" - -#ifdef __linux__ - #include -#endif - -using namespace qrcodegen; - -ShareConnectionLogic::ShareConnectionLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent), - m_textEditShareOpenVpnCodeText{}, - m_lineEditShareShadowSocksStringText{}, - m_shareShadowSocksQrCodeText{}, - m_textEditShareCloakText{}, - m_textEditShareAmneziaCodeText{} -{ -} - -void ShareConnectionLogic::onUpdatePage() -{ - set_textEditShareAmneziaCodeText(tr("")); - set_shareAmneziaQrCodeTextSeries({}); - set_shareAmneziaQrCodeTextSeriesLength(0); - - set_textEditShareOpenVpnCodeText(""); - - set_shareShadowSocksQrCodeText(""); - set_textEditShareShadowSocksText(""); - set_lineEditShareShadowSocksStringText(""); - - set_textEditShareCloakText(""); - - set_textEditShareWireGuardCodeText(""); - set_shareWireGuardQrCodeText(""); - - set_textEditShareIkev2CertText(""); - set_textEditShareIkev2MobileConfigText(""); - set_textEditShareIkev2StrongSwanConfigText(""); -} - -void ShareConnectionLogic::onPushButtonShareAmneziaGenerateClicked() -{ - set_textEditShareAmneziaCodeText(""); - set_shareAmneziaQrCodeTextSeries({}); - set_shareAmneziaQrCodeTextSeriesLength(0); - - QJsonObject serverConfig; - int serverIndex = uiLogic()->m_selectedServerIndex; - DockerContainer container = uiLogic()->m_selectedDockerContainer; - - // Full access - if (shareFullAccess()) { - serverConfig = m_settings->server(serverIndex); - } - // Container share - else { - ServerCredentials credentials = m_settings->serverCredentials(serverIndex); - QJsonObject containerConfig = m_settings->containerConfig(serverIndex, container); - 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); - } - - QByteArray ba; - if (!e) { - serverConfig = m_settings->server(serverIndex); - serverConfig.remove(config_key::userName); - serverConfig.remove(config_key::password); - serverConfig.remove(config_key::port); - serverConfig.insert(config_key::containers, QJsonArray {containerConfig}); - serverConfig.insert(config_key::defaultContainer, ContainerProps::containerToString(container)); - - auto dns = m_configurator->getDnsForConfig(serverIndex); - serverConfig.insert(config_key::dns1, dns.first); - serverConfig.insert(config_key::dns2, dns.second); - - } - else { - set_textEditShareAmneziaCodeText(tr("Error while generating connection profile")); - return; - } - } - - QByteArray ba = QJsonDocument(serverConfig).toJson(); - ba = qCompress(ba, 8); - QString code = QString("vpn://%1").arg(QString(ba.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals))); - set_textEditShareAmneziaCodeText(code); - - - QList qrChunks = genQrCodeImageSeries(ba); - set_shareAmneziaQrCodeTextSeries(qrChunks); - set_shareAmneziaQrCodeTextSeriesLength(qrChunks.size()); -} - -void ShareConnectionLogic::onPushButtonShareOpenVpnGenerateClicked() -{ - int serverIndex = uiLogic()->m_selectedServerIndex; - DockerContainer container = uiLogic()->m_selectedDockerContainer; - ServerCredentials credentials = m_settings->serverCredentials(serverIndex); - - const QJsonObject &containerConfig = m_settings->containerConfig(serverIndex, container); - - ErrorCode e = ErrorCode::NoError; - QString cfg = m_configurator->openVpnConfigurator->genOpenVpnConfig(credentials, container, containerConfig, &e); - cfg = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::OpenVpn, cfg); - - set_textEditShareOpenVpnCodeText(QJsonDocument::fromJson(cfg.toUtf8()).object()[config_key::config].toString()); -} - -void ShareConnectionLogic::onPushButtonShareShadowSocksGenerateClicked() -{ - int serverIndex = uiLogic()->m_selectedServerIndex; - DockerContainer container = uiLogic()->m_selectedDockerContainer; - ServerCredentials credentials = m_settings->serverCredentials(serverIndex); - - QJsonObject protoConfig = m_settings->protocolConfig(serverIndex, container, Proto::ShadowSocks); - QString cfg = protoConfig.value(config_key::last_config).toString(); - - if (cfg.isEmpty()) { - const QJsonObject &containerConfig = m_settings->containerConfig(serverIndex, container); - - ErrorCode e = ErrorCode::NoError; - cfg = m_configurator->shadowSocksConfigurator->genShadowSocksConfig(credentials, container, containerConfig, &e); - } - - QJsonObject ssConfig = QJsonDocument::fromJson(cfg.toUtf8()).object(); - - QString ssString = QString("%1:%2@%3:%4") - .arg(ssConfig.value("method").toString()) - .arg(ssConfig.value("password").toString()) - .arg(ssConfig.value("server").toString()) - .arg(ssConfig.value("server_port").toString()); - - ssString = "ss://" + ssString.toUtf8().toBase64(); - set_lineEditShareShadowSocksStringText(ssString); - - QrCode qr = QrCode::encodeText(ssString.toUtf8(), QrCode::Ecc::LOW); - QString svg = QString::fromStdString(toSvgString(qr, 0)); - - set_shareShadowSocksQrCodeText(svgToBase64(svg)); - - QString humanString = QString("Server: %3\n" - "Port: %4\n" - "Encryption: %1\n" - "Password: %2") - .arg(ssConfig.value("method").toString()) - .arg(ssConfig.value("password").toString()) - .arg(ssConfig.value("server").toString()) - .arg(ssConfig.value("server_port").toString()); - - set_textEditShareShadowSocksText(humanString); -} - -void ShareConnectionLogic::onPushButtonShareCloakGenerateClicked() -{ - int serverIndex = uiLogic()->m_selectedServerIndex; - DockerContainer container = uiLogic()->m_selectedDockerContainer; - ServerCredentials credentials = m_settings->serverCredentials(serverIndex); - - QJsonObject protoConfig = m_settings->protocolConfig(serverIndex, container, Proto::Cloak); - QString cfg = protoConfig.value(config_key::last_config).toString(); - - if (cfg.isEmpty()) { - const QJsonObject &containerConfig = m_settings->containerConfig(serverIndex, container); - - ErrorCode e = ErrorCode::NoError; - cfg = m_configurator->cloakConfigurator->genCloakConfig(credentials, container, containerConfig, &e); - } - - QJsonObject cloakConfig = QJsonDocument::fromJson(cfg.toUtf8()).object(); - cloakConfig.remove(config_key::transport_proto); - cloakConfig.insert("ProxyMethod", "shadowsocks"); - - set_textEditShareCloakText(QJsonDocument(cloakConfig).toJson()); -} - -void ShareConnectionLogic::onPushButtonShareWireGuardGenerateClicked() -{ - int serverIndex = uiLogic()->m_selectedServerIndex; - DockerContainer container = uiLogic()->m_selectedDockerContainer; - ServerCredentials credentials = m_settings->serverCredentials(serverIndex); - - const QJsonObject &containerConfig = m_settings->containerConfig(serverIndex, container); - - ErrorCode e = ErrorCode::NoError; - QString cfg = m_configurator->wireguardConfigurator->genWireguardConfig(credentials, container, containerConfig, &e); - if (e) { - emit uiLogic()->showWarningMessage(tr("Error occurred while generating the config.") + "\n" + - tr("Error message: ") + errorString(e) + "\n" + - tr("See logs for details.")); - return; - } - cfg = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::WireGuard, cfg); - cfg = QJsonDocument::fromJson(cfg.toUtf8()).object()[config_key::config].toString(); - - set_textEditShareWireGuardCodeText(cfg); - - QrCode qr = QrCode::encodeText(cfg.toUtf8(), QrCode::Ecc::LOW); - QString svg = QString::fromStdString(toSvgString(qr, 0)); - - set_shareWireGuardQrCodeText(svgToBase64(svg)); -} - -void ShareConnectionLogic::onPushButtonShareIkev2GenerateClicked() -{ - int serverIndex = uiLogic()->m_selectedServerIndex; - DockerContainer container = uiLogic()->m_selectedDockerContainer; - ServerCredentials credentials = m_settings->serverCredentials(serverIndex); - - Ikev2Configurator::ConnectionData connData = m_configurator->ikev2Configurator->prepareIkev2Config(credentials, container); - - QString cfg = m_configurator->ikev2Configurator->genIkev2Config(connData); - cfg = m_configurator->processConfigWithExportSettings(serverIndex, container, Proto::Ikev2, cfg); - cfg = QJsonDocument::fromJson(cfg.toUtf8()).object()[config_key::cert].toString(); - - set_textEditShareIkev2CertText(cfg); - - QString mobileCfg = m_configurator->ikev2Configurator->genMobileConfig(connData); - set_textEditShareIkev2MobileConfigText(mobileCfg); - - QString strongSwanCfg = m_configurator->ikev2Configurator->genStrongSwanConfig(connData); - set_textEditShareIkev2StrongSwanConfigText(strongSwanCfg); - -} - - -void ShareConnectionLogic::updateSharingPage(int serverIndex, DockerContainer container) -{ - uiLogic()->m_selectedDockerContainer = container; - uiLogic()->m_selectedServerIndex = serverIndex; - set_shareFullAccess(container == DockerContainer::None); - - m_shareAmneziaQrCodeTextSeries.clear(); - set_shareAmneziaQrCodeTextSeriesLength(0); -} - -QList ShareConnectionLogic::genQrCodeImageSeries(const QByteArray &data) -{ - double k = 850; - - quint8 chunksCount = std::ceil(data.size() / k); - QList 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); - - QrCode qr = QrCode::encodeText(ba, QrCode::Ecc::LOW); - QString svg = QString::fromStdString(toSvgString(qr, 0)); - chunks.append(svgToBase64(svg)); - } - - return chunks; -} - -QString ShareConnectionLogic::svgToBase64(const QString &image) -{ - return "data:image/svg;base64," + QString::fromLatin1(image.toUtf8().toBase64().data()); -} diff --git a/client/ui/pages_logic/ShareConnectionLogic.h b/client/ui/pages_logic/ShareConnectionLogic.h deleted file mode 100644 index 3b9655aa..00000000 --- a/client/ui/pages_logic/ShareConnectionLogic.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef SHARE_CONNECTION_LOGIC_H -#define SHARE_CONNECTION_LOGIC_H - -#include "PageLogicBase.h" -#include "containers/containers_defs.h" - -class UiLogic; - -class ShareConnectionLogic: public PageLogicBase -{ - Q_OBJECT - -public: - AUTO_PROPERTY(bool, shareFullAccess) - - AUTO_PROPERTY(QString, textEditShareAmneziaCodeText) - AUTO_PROPERTY(QStringList, shareAmneziaQrCodeTextSeries) - AUTO_PROPERTY(int, shareAmneziaQrCodeTextSeriesLength) - - AUTO_PROPERTY(QString, textEditShareOpenVpnCodeText) - - AUTO_PROPERTY(QString, textEditShareShadowSocksText) - AUTO_PROPERTY(QString, lineEditShareShadowSocksStringText) - AUTO_PROPERTY(QString, shareShadowSocksQrCodeText) - - AUTO_PROPERTY(QString, textEditShareCloakText) - - AUTO_PROPERTY(QString, textEditShareWireGuardCodeText) - AUTO_PROPERTY(QString, shareWireGuardQrCodeText) - - AUTO_PROPERTY(QString, textEditShareIkev2CertText) - AUTO_PROPERTY(QString, textEditShareIkev2MobileConfigText) - AUTO_PROPERTY(QString, textEditShareIkev2StrongSwanConfigText) - -public: - Q_INVOKABLE void onPushButtonShareAmneziaGenerateClicked(); - Q_INVOKABLE void onPushButtonShareOpenVpnGenerateClicked(); - Q_INVOKABLE void onPushButtonShareShadowSocksGenerateClicked(); - Q_INVOKABLE void onPushButtonShareCloakGenerateClicked(); - Q_INVOKABLE void onPushButtonShareWireGuardGenerateClicked(); - Q_INVOKABLE void onPushButtonShareIkev2GenerateClicked(); - - Q_INVOKABLE virtual void onUpdatePage() override; - -public: - explicit ShareConnectionLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~ShareConnectionLogic() = default; - - void updateSharingPage(int serverIndex, DockerContainer container); - QList genQrCodeImageSeries(const QByteArray &data); - - QString svgToBase64(const QString &image); -}; -#endif // SHARE_CONNECTION_LOGIC_H diff --git a/client/ui/pages_logic/SitesLogic.cpp b/client/ui/pages_logic/SitesLogic.cpp deleted file mode 100644 index aaf8f73d..00000000 --- a/client/ui/pages_logic/SitesLogic.cpp +++ /dev/null @@ -1,211 +0,0 @@ -#include -#include -#include -#include - -#include "SitesLogic.h" -#include "VpnLogic.h" -#include "utilities.h" -#include "vpnconnection.h" -#include - -#include "../models/sites_model.h" -#include "../uilogic.h" - -SitesLogic::SitesLogic(UiLogic *logic, QObject *parent) - : PageLogicBase(logic, parent), - m_labelSitesAddCustomText {}, - m_tableViewSitesModel { nullptr }, - m_lineEditSitesAddCustomText {} -{ - // sitesModels.insert(Settings::VpnOnlyForwardSites, new SitesModel(m_settings, Settings::VpnOnlyForwardSites)); - // sitesModels.insert(Settings::VpnAllExceptSites, new SitesModel(m_settings, Settings::VpnAllExceptSites)); -} - -void SitesLogic::onUpdatePage() -{ - Settings::RouteMode m = m_settings->routeMode(); - if (m == Settings::VpnAllSites) - return; - - if (m == Settings::VpnOnlyForwardSites) { - set_labelSitesAddCustomText(tr("These sites will be opened using VPN")); - } - if (m == Settings::VpnAllExceptSites) { - set_labelSitesAddCustomText(tr("These sites will be excepted from VPN")); - } - - set_tableViewSitesModel(sitesModels.value(m)); - // sitesModels.value(m)->resetCache(); -} - -void SitesLogic::onPushButtonAddCustomSitesClicked() -{ - if (uiLogic()->pageLogic()->radioButtonVpnModeAllSitesChecked()) { - return; - } - Settings::RouteMode mode = m_settings->routeMode(); - - QString newSite = lineEditSitesAddCustomText(); - - if (newSite.isEmpty()) - return; - if (!newSite.contains(".")) - return; - - if (!Utils::ipAddressWithSubnetRegExp().exactMatch(newSite)) { - // get domain name if it present - newSite.replace("https://", ""); - newSite.replace("http://", ""); - newSite.replace("ftp://", ""); - - newSite = newSite.split("/", Qt::SkipEmptyParts).first(); - } - - const auto &cbProcess = [this, mode](const QString &newSite, const QString &ip) { - m_settings->addVpnSite(mode, newSite, ip); - - if (!ip.isEmpty()) { - QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "addRoutes", Qt::QueuedConnection, - Q_ARG(QStringList, QStringList() << ip)); - } else if (Utils::ipAddressWithSubnetRegExp().exactMatch(newSite)) { - QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "addRoutes", Qt::QueuedConnection, - Q_ARG(QStringList, QStringList() << newSite)); - } - - QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "flushDns", Qt::QueuedConnection); - - onUpdatePage(); - }; - - const auto &cbResolv = [this, cbProcess](const QHostInfo &hostInfo) { - const QList &addresses = hostInfo.addresses(); - QString ipv4Addr; - for (const QHostAddress &addr : hostInfo.addresses()) { - if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) { - cbProcess(hostInfo.hostName(), addr.toString()); - break; - } - } - }; - - set_lineEditSitesAddCustomText(""); - - if (Utils::ipAddressWithSubnetRegExp().exactMatch(newSite)) { - cbProcess(newSite, ""); - return; - } else { - cbProcess(newSite, ""); - onUpdatePage(); - QHostInfo::lookupHost(newSite, this, cbResolv); - } -} - -void SitesLogic::onPushButtonSitesDeleteClicked(QStringList items) -{ - Settings::RouteMode mode = m_settings->routeMode(); - - auto siteModel = qobject_cast(tableViewSitesModel()); - if (!siteModel || items.isEmpty()) { - return; - } - - QStringList sites; - QStringList ips; - - for (const QString &s : items) { - bool ok; - int row = s.toInt(&ok); - if (!ok || row < 0 || row >= siteModel->rowCount()) - return; - // sites.append(siteModel->data(row, 0).toString()); - - // if (uiLogic()->m_vpnConnection && uiLogic()->m_vpnConnection->connectionState() == VpnProtocol::Connected) { - // ips.append(siteModel->data(row, 1).toString()); - // } - } - - m_settings->removeVpnSites(mode, sites); - - QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "deleteRoutes", Qt::QueuedConnection, Q_ARG(QStringList, ips)); - - QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "flushDns", Qt::QueuedConnection); - - onUpdatePage(); -} - -void SitesLogic::onPushButtonSitesImportClicked(const QString &fileName) -{ - QFile file(QUrl { fileName }.toLocalFile()); - if (!file.open(QIODevice::ReadOnly)) { - qDebug() << "Can't open file " << QUrl { fileName }.toLocalFile(); - return; - } - - Settings::RouteMode mode = m_settings->routeMode(); - - QStringList ips; - QMap sites; - - while (!file.atEnd()) { - QString line = file.readLine(); - QStringList line_ips; - QStringList line_sites; - - int posDomain = 0; - QRegExp domainRx = Utils::domainRegExp(); - while ((posDomain = domainRx.indexIn(line, posDomain)) != -1) { - line_sites.append(domainRx.cap(0)); - posDomain += domainRx.matchedLength(); - } - - int posIp = 0; - QRegExp ipRx = Utils::ipAddressWithSubnetRegExp(); - while ((posIp = ipRx.indexIn(line, posIp)) != -1) { - line_ips.append(ipRx.cap(0)); - posIp += ipRx.matchedLength(); - } - - // domain regex cover ip regex, so remove ips from sites - for (const QString &ip : line_ips) { - line_sites.removeAll(ip); - } - - if (line_sites.size() == 1 && line_ips.size() == 1) { - sites.insert(line_sites.at(0), line_ips.at(0)); - } else if (line_sites.size() > 0 && line_ips.size() == 0) { - for (const QString &site : line_sites) { - sites.insert(site, ""); - } - } else { - for (const QString &site : line_sites) { - sites.insert(site, ""); - } - for (const QString &ip : line_ips) { - ips.append(ip); - } - } - } - - m_settings->addVpnIps(mode, ips); - m_settings->addVpnSites(mode, sites); - - QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "addRoutes", Qt::QueuedConnection, Q_ARG(QStringList, ips)); - - QMetaObject::invokeMethod(uiLogic()->m_vpnConnection, "flushDns", Qt::QueuedConnection); - - onUpdatePage(); -} - -void SitesLogic::onPushButtonSitesExportClicked() -{ - Settings::RouteMode mode = m_settings->routeMode(); - - QVariantMap sites = m_settings->vpnSites(mode); - - QString data; - for (auto s : sites.keys()) { - data += s + "\t" + sites.value(s).toString() + "\n"; - } - uiLogic()->saveTextFile("Export Sites", "sites.txt", ".txt", data); -} diff --git a/client/ui/pages_logic/SitesLogic.h b/client/ui/pages_logic/SitesLogic.h deleted file mode 100644 index 35bf1f90..00000000 --- a/client/ui/pages_logic/SitesLogic.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef SITES_LOGIC_H -#define SITES_LOGIC_H - -#include "PageLogicBase.h" -#include "settings.h" - -class UiLogic; -class SitesModel; - -class SitesLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(QString, labelSitesAddCustomText) - AUTO_PROPERTY(QObject*, tableViewSitesModel) - AUTO_PROPERTY(QString, lineEditSitesAddCustomText) - -public: - Q_INVOKABLE void onUpdatePage() override; - - Q_INVOKABLE void onPushButtonAddCustomSitesClicked(); - Q_INVOKABLE void onPushButtonSitesDeleteClicked(QStringList items); - Q_INVOKABLE void onPushButtonSitesImportClicked(const QString &fileName); - Q_INVOKABLE void onPushButtonSitesExportClicked(); - - -public: - explicit SitesLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~SitesLogic() = default; - - QMap sitesModels; -}; -#endif // SITES_LOGIC_H diff --git a/client/ui/pages_logic/StartPageLogic.cpp b/client/ui/pages_logic/StartPageLogic.cpp deleted file mode 100644 index 891d67fb..00000000 --- a/client/ui/pages_logic/StartPageLogic.cpp +++ /dev/null @@ -1,374 +0,0 @@ -#include "StartPageLogic.h" -#include "ViewConfigLogic.h" - -#include "../uilogic.h" -#include "configurators/ssh_configurator.h" -#include "configurators/vpn_configurator.h" -#include "core/errorstrings.h" -#include "core/servercontroller.h" -#include "utilities.h" - -#include -#include -#include - -#ifdef Q_OS_ANDROID - #include "../../platforms/android/android_controller.h" - #include "../../platforms/android/androidutils.h" - #include -#endif - -#ifdef Q_OS_IOS - #include -#endif - -namespace -{ - enum class ConfigTypes { - Amnezia, - OpenVpn, - WireGuard - }; - - ConfigTypes checkConfigFormat(const QString &config) - { - const QString openVpnConfigPatternCli = "client"; - const QString openVpnConfigPatternProto1 = "proto tcp"; - const QString openVpnConfigPatternProto2 = "proto udp"; - const QString openVpnConfigPatternDriver1 = "dev tun"; - const QString openVpnConfigPatternDriver2 = "dev tap"; - - const QString wireguardConfigPatternSectionInterface = "[Interface]"; - const QString wireguardConfigPatternSectionPeer = "[Peer]"; - - if (config.contains(openVpnConfigPatternCli) - && (config.contains(openVpnConfigPatternProto1) || config.contains(openVpnConfigPatternProto2)) - && (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) { - return ConfigTypes::OpenVpn; - } else if (config.contains(wireguardConfigPatternSectionInterface) - && config.contains(wireguardConfigPatternSectionPeer)) - return ConfigTypes::WireGuard; - return ConfigTypes::Amnezia; - } - -} - -StartPageLogic::StartPageLogic(UiLogic *logic, QObject *parent) - : PageLogicBase(logic, parent), - m_pushButtonConnectEnabled { true }, - m_pushButtonConnectText { tr("Connect") }, - m_pushButtonConnectKeyChecked { false }, - m_labelWaitInfoVisible { true }, - m_pushButtonBackFromStartVisible { true }, - m_ipAddressPortRegex { Utils::ipAddressPortRegExp() } -{ -#ifdef Q_OS_ANDROID - // Set security screen for Android app - AndroidUtils::runOnAndroidThreadSync([]() { - QJniObject activity = AndroidUtils::getActivity(); - QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;"); - if (window.isValid()) { - const int FLAG_SECURE = 8192; - window.callMethod("addFlags", "(I)V", FLAG_SECURE); - } - }); -#endif -} - -void StartPageLogic::onUpdatePage() -{ - set_lineEditStartExistingCodeText(""); - set_textEditSshKeyText(""); - set_lineEditIpText(""); - set_lineEditPasswordText(""); - set_textEditSshKeyText(""); - set_lineEditLoginText(""); - - set_labelWaitInfoVisible(false); - set_labelWaitInfoText(""); - - set_pushButtonConnectKeyChecked(false); - - set_pushButtonBackFromStartVisible(uiLogic()->pagesStackDepth() > 0); -} - -void StartPageLogic::onPushButtonConnect() -{ - if (pushButtonConnectKeyChecked()) { - if (lineEditIpText().isEmpty() || lineEditLoginText().isEmpty() || textEditSshKeyText().isEmpty()) { - set_labelWaitInfoText(tr("Please fill in all fields")); - return; - } - } else { - if (lineEditIpText().isEmpty() || lineEditLoginText().isEmpty() || lineEditPasswordText().isEmpty()) { - set_labelWaitInfoText(tr("Please fill in all fields")); - return; - } - } - - ServerCredentials serverCredentials; - serverCredentials.hostName = lineEditIpText(); - if (serverCredentials.hostName.contains(":")) { - serverCredentials.port = serverCredentials.hostName.split(":").at(1).toInt(); - serverCredentials.hostName = serverCredentials.hostName.split(":").at(0); - } - serverCredentials.userName = lineEditLoginText(); - if (pushButtonConnectKeyChecked()) { - QString key = textEditSshKeyText(); - if (key.startsWith("ssh-rsa")) { - emit uiLogic()->showPublicKeyWarning(); - return; - } - - if (key.contains("OPENSSH") && key.contains("BEGIN") && key.contains("PRIVATE KEY")) { - key = m_configurator->sshConfigurator->convertOpenSShKey(key); - } - - serverCredentials.secretData = key; - } else { - serverCredentials.secretData = lineEditPasswordText(); - } - - set_pushButtonConnectEnabled(false); - set_pushButtonConnectText(tr("Connecting...")); - - ServerController serverController(m_settings); - ErrorCode errorCode = ErrorCode::NoError; - - if (pushButtonConnectKeyChecked()) { - auto passphraseCallback = [this, &serverController]() { - emit showPassphraseRequestMessage(); - QEventLoop loop; - QObject::connect(this, &StartPageLogic::passphraseDialogClosed, &loop, &QEventLoop::quit); - loop.exec(); - - return m_privateKeyPassphrase; - }; - - QString decryptedPrivateKey; - errorCode = serverController.getDecryptedPrivateKey(serverCredentials, decryptedPrivateKey, passphraseCallback); - if (errorCode == ErrorCode::NoError) { - serverCredentials.secretData = decryptedPrivateKey; - } - } - - QString output; - if (errorCode == ErrorCode::NoError) { - output = serverController.checkSshConnection(serverCredentials, &errorCode); - } - - bool ok = true; - if (errorCode) { - set_labelWaitInfoVisible(true); - set_labelWaitInfoText(errorString(errorCode)); - ok = false; - } else { - if (output.contains("Please login as the user")) { - output.replace("\n", ""); - set_labelWaitInfoVisible(true); - set_labelWaitInfoText(output); - ok = false; - } - } - - set_pushButtonConnectEnabled(true); - set_pushButtonConnectText(tr("Connect")); - - uiLogic()->m_installCredentials = serverCredentials; - if (ok) - emit uiLogic()->goToPage(Page::NewServer); -} - -void StartPageLogic::onPushButtonImport() -{ - importConnectionFromCode(lineEditStartExistingCodeText()); -} - -void StartPageLogic::onPushButtonImportOpenFile() -{ - QString fileName = UiLogic::getOpenFileName(Q_NULLPTR, tr("Open config file"), - QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), - "*.vpn *.ovpn *.conf"); - if (fileName.isEmpty()) - return; - - QFile file(fileName); - -#ifdef Q_OS_IOS - CFURLRef url = CFURLCreateWithFileSystemPath( - kCFAllocatorDefault, - CFStringCreateWithCharacters(0, reinterpret_cast(fileName.unicode()), fileName.length()), - kCFURLPOSIXPathStyle, 0); - - if (!CFURLStartAccessingSecurityScopedResource(url)) { - qDebug() << "Could not access path " << QUrl::fromLocalFile(fileName).toString(); - } -#endif - - file.open(QIODevice::ReadOnly); - QByteArray data = file.readAll(); - - importAnyFile(QString(data)); -} - -#ifdef Q_OS_ANDROID -void StartPageLogic::startQrDecoder() -{ - AndroidController::instance()->startQrReaderActivity(); -} -#endif - -void StartPageLogic::importAnyFile(const QString &configData) -{ - auto configFormat = checkConfigFormat(configData); - if (configFormat == ConfigTypes::OpenVpn) { - importConnectionFromOpenVpnConfig(configData); - } else if (configFormat == ConfigTypes::WireGuard) { - importConnectionFromWireguardConfig(configData); - } else { - importConnectionFromCode(configData); - } -} - -bool StartPageLogic::importConnection(const QJsonObject &profile) -{ - ServerCredentials credentials; - credentials.hostName = profile.value(config_key::hostName).toString(); - credentials.port = profile.value(config_key::port).toInt(); - credentials.userName = profile.value(config_key::userName).toString(); - credentials.secretData = profile.value(config_key::password).toString(); - - if (credentials.isValid() || profile.contains(config_key::containers)) { - // check config - uiLogic()->pageLogic()->set_configJson(profile); - emit uiLogic()->goToPage(Page::ViewConfig); - } else { - qDebug() << "Failed to import profile"; - qDebug().noquote() << QJsonDocument(profile).toJson(); - return false; - } - - return true; -} - -bool StartPageLogic::importConnectionFromCode(QString code) -{ - code.replace("vpn://", ""); - QByteArray ba = QByteArray::fromBase64(code.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); - - QByteArray ba_uncompressed = qUncompress(ba); - if (!ba_uncompressed.isEmpty()) { - ba = ba_uncompressed; - } - - QJsonObject o; - o = QJsonDocument::fromJson(ba).object(); - if (!o.isEmpty()) { - return importConnection(o); - } - - return false; -} - -bool StartPageLogic::importConnectionFromQr(const QByteArray &data) -{ - QJsonObject dataObj = QJsonDocument::fromJson(data).object(); - if (!dataObj.isEmpty()) { - return importConnection(dataObj); - } - - QByteArray ba_uncompressed = qUncompress(data); - if (!ba_uncompressed.isEmpty()) { - return importConnection(QJsonDocument::fromJson(ba_uncompressed).object()); - } - - return false; -} - -bool StartPageLogic::importConnectionFromOpenVpnConfig(const QString &config) -{ - QJsonObject openVpnConfig; - openVpnConfig[config_key::config] = config; - - QJsonObject lastConfig; - lastConfig[config_key::last_config] = QString(QJsonDocument(openVpnConfig).toJson()); - lastConfig[config_key::isThirdPartyConfig] = true; - - QJsonObject containers; - containers.insert(config_key::container, QJsonValue("amnezia-openvpn")); - containers.insert(config_key::openvpn, QJsonValue(lastConfig)); - - QJsonArray arr; - arr.push_back(containers); - - QString hostName; - const static QRegularExpression hostNameRegExp("remote (.*) [0-9]*"); - QRegularExpressionMatch hostNameMatch = hostNameRegExp.match(config); - if (hostNameMatch.hasMatch()) { - hostName = hostNameMatch.captured(1); - } - - QJsonObject o; - o[config_key::containers] = arr; - o[config_key::defaultContainer] = "amnezia-openvpn"; - o[config_key::description] = m_settings->nextAvailableServerName(); - - const static QRegularExpression dnsRegExp("dhcp-option DNS (\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)"); - QRegularExpressionMatchIterator dnsMatch = dnsRegExp.globalMatch(config); - if (dnsMatch.hasNext()) { - o[config_key::dns1] = dnsMatch.next().captured(1); - } - if (dnsMatch.hasNext()) { - o[config_key::dns2] = dnsMatch.next().captured(1); - } - - o[config_key::hostName] = hostName; - - return importConnection(o); -} - -bool StartPageLogic::importConnectionFromWireguardConfig(const QString &config) -{ - QJsonObject lastConfig; - lastConfig[config_key::config] = config; - - const static QRegularExpression hostNameAndPortRegExp("Endpoint = (.*):([0-9]*)"); - QRegularExpressionMatch hostNameAndPortMatch = hostNameAndPortRegExp.match(config); - QString hostName; - QString port; - if (hostNameAndPortMatch.hasMatch()) { - hostName = hostNameAndPortMatch.captured(1); - port = hostNameAndPortMatch.captured(2); - } - - QJsonObject wireguardConfig; - wireguardConfig[config_key::last_config] = QString(QJsonDocument(lastConfig).toJson()); - wireguardConfig[config_key::isThirdPartyConfig] = true; - wireguardConfig[config_key::port] = port; - wireguardConfig[config_key::transport_proto] = "udp"; - - QJsonObject containers; - containers.insert(config_key::container, QJsonValue("amnezia-wireguard")); - containers.insert(config_key::wireguard, QJsonValue(wireguardConfig)); - - QJsonArray arr; - arr.push_back(containers); - - QJsonObject o; - o[config_key::containers] = arr; - o[config_key::defaultContainer] = "amnezia-wireguard"; - o[config_key::description] = m_settings->nextAvailableServerName(); - - const static QRegularExpression dnsRegExp( - "DNS = " - "(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b).*(\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b)"); - QRegularExpressionMatch dnsMatch = dnsRegExp.match(config); - if (dnsMatch.hasMatch()) { - o[config_key::dns1] = dnsMatch.captured(1); - o[config_key::dns2] = dnsMatch.captured(2); - } - - o[config_key::hostName] = hostName; - - return importConnection(o); -} diff --git a/client/ui/pages_logic/StartPageLogic.h b/client/ui/pages_logic/StartPageLogic.h deleted file mode 100644 index 9025a052..00000000 --- a/client/ui/pages_logic/StartPageLogic.h +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef START_PAGE_LOGIC_H -#define START_PAGE_LOGIC_H - -#include "PageLogicBase.h" - -#include - -class UiLogic; - -class StartPageLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(bool, pushButtonConnectEnabled) - AUTO_PROPERTY(bool, pushButtonConnectKeyChecked) - AUTO_PROPERTY(QString, pushButtonConnectText) - AUTO_PROPERTY(QString, lineEditStartExistingCodeText) - AUTO_PROPERTY(QString, textEditSshKeyText) - AUTO_PROPERTY(QString, lineEditIpText) - AUTO_PROPERTY(QString, lineEditPasswordText) - AUTO_PROPERTY(QString, lineEditLoginText) - AUTO_PROPERTY(bool, labelWaitInfoVisible) - AUTO_PROPERTY(QString, labelWaitInfoText) - AUTO_PROPERTY(bool, pushButtonBackFromStartVisible) - - AUTO_PROPERTY(QString, privateKeyPassphrase) - - READONLY_PROPERTY(QRegularExpression, ipAddressPortRegex) -public: - Q_INVOKABLE void onUpdatePage() override; - - Q_INVOKABLE void onPushButtonConnect(); - Q_INVOKABLE void onPushButtonImport(); - Q_INVOKABLE void onPushButtonImportOpenFile(); - -#ifdef Q_OS_ANDROID - Q_INVOKABLE void startQrDecoder(); -#endif - - void importAnyFile(const QString &configData); - - bool importConnection(const QJsonObject &profile); - bool importConnectionFromCode(QString code); - bool importConnectionFromQr(const QByteArray &data); - bool importConnectionFromOpenVpnConfig(const QString &config); - bool importConnectionFromWireguardConfig(const QString &config); - -public: - explicit StartPageLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~StartPageLogic() = default; - -signals: - void showPassphraseRequestMessage(); - void passphraseDialogClosed(); -}; -#endif // START_PAGE_LOGIC_H diff --git a/client/ui/pages_logic/ViewConfigLogic.cpp b/client/ui/pages_logic/ViewConfigLogic.cpp deleted file mode 100644 index 5f711498..00000000 --- a/client/ui/pages_logic/ViewConfigLogic.cpp +++ /dev/null @@ -1,93 +0,0 @@ -#include "ViewConfigLogic.h" -#include "core/errorstrings.h" -#include "../uilogic.h" - - -ViewConfigLogic::ViewConfigLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent) -{ - -} - -void ViewConfigLogic::onUpdatePage() -{ - set_configText(QJsonDocument(configJson()).toJson()); - - auto s = configJson()[config_key::isThirdPartyConfig].toBool(); - - m_openVpnLastConfigs = m_openVpnMalStrings = - "
"; - - m_warningStringNumber = 3; - m_warningActive = false; - - const QJsonArray &containers = configJson()[config_key::containers].toArray(); - int i = 0; - for (const QJsonValue &v: containers) { - auto containerName = v.toObject()[config_key::container].toString(); - QJsonObject containerConfig = v.toObject()[containerName.replace("amnezia-", "")].toObject(); - if (containerConfig[config_key::isThirdPartyConfig].toBool()) { - auto lastConfig = containerConfig.value(config_key::last_config).toString(); - auto lastConfigJson = QJsonDocument::fromJson(lastConfig.toUtf8()).object(); - QStringList lines = lastConfigJson.value(config_key::config).toString().replace("\r", "").split("\n"); - QString lastConfigText; - for (const QString &l: lines) { - lastConfigText.append(l + "\n"); - } - set_configText(lastConfigText); - } - - - if (v.toObject()[config_key::container].toString() == "amnezia-openvpn") { - QString lastConfig = v.toObject()[ProtocolProps::protoToString(Proto::OpenVpn)] - .toObject()[config_key::last_config].toString(); - - QString lastConfigJson = QJsonDocument::fromJson(lastConfig.toUtf8()).object()[config_key::config] - .toString(); - - QStringList lines = lastConfigJson.replace("\r", "").split("\n"); - for (const QString &l: lines) { - i++; - QRegularExpressionMatch match = m_re.match(l); - if (dangerousTags.contains(match.captured(0))) { - QString t = QString("

%1").arg(l); - m_openVpnLastConfigs.append(t + "\n"); - m_openVpnMalStrings.append(t); - if (m_warningStringNumber == 3) m_warningStringNumber = i - 3; - m_warningActive = true; - qDebug() << "ViewConfigLogic : malicious scripts warning:" << l; - } - else { - m_openVpnLastConfigs.append("

" + l + " \n"); - } - } - } - } - - emit openVpnLastConfigsChanged(m_openVpnLastConfigs); - emit openVpnMalStringsChanged(m_openVpnMalStrings); - emit warningStringNumberChanged(m_warningStringNumber); - emit warningActiveChanged(m_warningActive); -} - -void ViewConfigLogic::importConfig() -{ - m_settings->addServer(configJson()); - m_settings->setDefaultServer(m_settings->serversCount() - 1); - - - if (!configJson().contains(config_key::containers) || configJson().value(config_key::containers).toArray().isEmpty()) { - uiLogic()->m_selectedServerIndex = m_settings->defaultServerIndex(); - uiLogic()->m_selectedDockerContainer = m_settings->defaultContainer(uiLogic()->m_selectedServerIndex); - uiLogic()->onUpdateAllPages(); - emit uiLogic()->goToPage(Page::Vpn); - emit uiLogic()->setStartPage(Page::Vpn); - emit uiLogic()->goToPage(Page::ServerContainers); - } else { - emit uiLogic()->goToPage(Page::Vpn); - emit uiLogic()->setStartPage(Page::Vpn); - } -} - diff --git a/client/ui/pages_logic/ViewConfigLogic.h b/client/ui/pages_logic/ViewConfigLogic.h deleted file mode 100644 index 4713158e..00000000 --- a/client/ui/pages_logic/ViewConfigLogic.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef VIEW_CONFIG_LOGIC_H -#define VIEW_CONFIG_LOGIC_H - -#include "PageLogicBase.h" - -#include - -class UiLogic; - -class ViewConfigLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(QString, configText) - AUTO_PROPERTY(QString, openVpnLastConfigs) - AUTO_PROPERTY(QString, openVpnMalStrings) - AUTO_PROPERTY(QJsonObject, configJson) - AUTO_PROPERTY(int, warningStringNumber) - AUTO_PROPERTY(bool, warningActive) - -public: - Q_INVOKABLE void onUpdatePage() override; - Q_INVOKABLE void importConfig(); - - -public: - explicit ViewConfigLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~ViewConfigLogic() = default; - -private: - QRegularExpression m_re {"(\\w+-\\w+|\\w+)"}; - - // https://github.com/OpenVPN/openvpn/blob/master/doc/man-sections/script-options.rst - QStringList dangerousTags { - "up", - "tls-verify", - "ipchange", - "client-connect", - "route-up", - "route-pre-down", - "client-disconnect", - "down", - "learn-address", - "auth-user-pass-verify" - }; -}; -#endif // VIEW_CONFIG_LOGIC_H diff --git a/client/ui/pages_logic/VpnLogic.cpp b/client/ui/pages_logic/VpnLogic.cpp deleted file mode 100644 index 2f5e7b65..00000000 --- a/client/ui/pages_logic/VpnLogic.cpp +++ /dev/null @@ -1,245 +0,0 @@ -#include - -#include "VpnLogic.h" - -#include "core/errorstrings.h" -#include "vpnconnection.h" -#include -#include -#include "../uilogic.h" -#include "version.h" -#include - - -VpnLogic::VpnLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent), - m_pushButtonConnectChecked{false}, - - m_radioButtonVpnModeAllSitesChecked{true}, - m_radioButtonVpnModeForwardSitesChecked{false}, - m_radioButtonVpnModeExceptSitesChecked{false}, - - m_labelSpeedReceivedText{tr("0 Mbps")}, - m_labelSpeedSentText{tr("0 Mbps")}, - m_labelStateText{}, - m_isContainerHaveAuthData{false}, - m_isContainerSupportedByCurrentPlatform{false}, - m_widgetVpnModeEnabled{false} -{ - connect(uiLogic()->m_vpnConnection, &VpnConnection::bytesChanged, this, &VpnLogic::onBytesChanged); - connect(uiLogic()->m_vpnConnection, &VpnConnection::connectionStateChanged, this, &VpnLogic::onConnectionStateChanged); - connect(uiLogic()->m_vpnConnection, &VpnConnection::vpnProtocolError, this, &VpnLogic::onVpnProtocolError); - - connect(this, &VpnLogic::connectToVpn, uiLogic()->m_vpnConnection, &VpnConnection::connectToVpn, Qt::QueuedConnection); - connect(this, &VpnLogic::disconnectFromVpn, uiLogic()->m_vpnConnection, &VpnConnection::disconnectFromVpn, Qt::QueuedConnection); - - connect(m_settings.get(), &Settings::saveLogsChanged, this, &VpnLogic::onUpdatePage); - - if (m_settings->isAutoConnect() && m_settings->defaultServerIndex() >= 0) { - QTimer::singleShot(1000, this, [this](){ - set_pushButtonConnectEnabled(false); - onConnect(); - }); - } - else { - onConnectionStateChanged(Vpn::ConnectionState::Disconnected); - } -} - - -void VpnLogic::onUpdatePage() -{ - Settings::RouteMode mode = m_settings->routeMode(); - DockerContainer selectedContainer = m_settings->defaultContainer(m_settings->defaultServerIndex()); - - set_isCustomRoutesSupported (selectedContainer == DockerContainer::OpenVpn || - selectedContainer == DockerContainer::ShadowSocks|| - selectedContainer == DockerContainer::Cloak); - - set_isContainerHaveAuthData(m_settings->haveAuthData(m_settings->defaultServerIndex())); - - set_radioButtonVpnModeAllSitesChecked(mode == Settings::VpnAllSites || !isCustomRoutesSupported()); - set_radioButtonVpnModeForwardSitesChecked(mode == Settings::VpnOnlyForwardSites && isCustomRoutesSupported()); - set_radioButtonVpnModeExceptSitesChecked(mode == Settings::VpnAllExceptSites && isCustomRoutesSupported()); - - const QJsonObject &server = uiLogic()->m_settings->defaultServer(); - QString serverString = QString("%2 (%3)") - .arg(server.value(config_key::description).toString()) - .arg(server.value(config_key::hostName).toString()); - set_labelCurrentServer(serverString); - - QString selectedContainerName = ContainerProps::containerHumanNames().value(selectedContainer); - set_labelCurrentService(selectedContainerName); - - auto dns = m_configurator->getDnsForConfig(m_settings->defaultServerIndex()); - set_amneziaDnsEnabled(dns.first == protocols::dns::amneziaDnsIp); - if (dns.first == protocols::dns::amneziaDnsIp) { - set_labelCurrentDns("On your server"); - } - else { - set_labelCurrentDns(dns.first + ", " + dns.second); - } - - set_isContainerSupportedByCurrentPlatform(ContainerProps::isSupportedByCurrentPlatform(selectedContainer)); - if (!isContainerSupportedByCurrentPlatform()) { - set_labelErrorText(tr("AmneziaVPN not supporting selected protocol on this device. Select another protocol.")); - } - else { - set_labelErrorText(""); - } - QString ver = QString("v. %2").arg(QString(APP_MAJOR_VERSION)); - set_labelVersionText(ver); - - set_labelLogEnabledVisible(m_settings->isSaveLogs()); -} - - -void VpnLogic::onRadioButtonVpnModeAllSitesClicked() -{ - m_settings->setRouteMode(Settings::VpnAllSites); - onUpdatePage(); -} - -void VpnLogic::onRadioButtonVpnModeForwardSitesClicked() -{ - m_settings->setRouteMode(Settings::VpnOnlyForwardSites); - onUpdatePage(); -} - -void VpnLogic::onRadioButtonVpnModeExceptSitesClicked() -{ - m_settings->setRouteMode(Settings::VpnAllExceptSites); - onUpdatePage(); -} - -void VpnLogic::onBytesChanged(quint64 receivedData, quint64 sentData) -{ - set_labelSpeedReceivedText(VpnConnection::bytesPerSecToText(receivedData)); - set_labelSpeedSentText(VpnConnection::bytesPerSecToText(sentData)); -} - -void VpnLogic::onConnectionStateChanged(Vpn::ConnectionState state) -{ - qDebug() << "VpnLogic::onConnectionStateChanged" << VpnProtocol::textConnectionState(state); - if (uiLogic()->m_vpnConnection == NULL) { - qDebug() << "VpnLogic::onConnectionStateChanged" << VpnProtocol::textConnectionState(state) << "невозможно, соединение отсутствует (уничтожено ранее)"; - return; - } - bool pbConnectEnabled = false; - bool pbConnectChecked = false; - - bool rbModeEnabled = false; - bool pbConnectVisible = false; - set_labelStateText(VpnProtocol::textConnectionState(state)); - - switch (state) { - case Vpn::ConnectionState::Disconnected: - onBytesChanged(0,0); - pbConnectChecked = false; - pbConnectEnabled = true; - pbConnectVisible = true; - rbModeEnabled = true; - break; - case Vpn::ConnectionState::Preparing: - pbConnectChecked = true; - pbConnectEnabled = false; - pbConnectVisible = false; - rbModeEnabled = false; - break; - case Vpn::ConnectionState::Connecting: - pbConnectChecked = true; - pbConnectEnabled = true; - pbConnectVisible = false; - rbModeEnabled = false; - break; - case Vpn::ConnectionState::Connected: - pbConnectChecked = true; - pbConnectEnabled = true; - pbConnectVisible = true; - rbModeEnabled = false; - break; - case Vpn::ConnectionState::Disconnecting: - pbConnectChecked = false; - pbConnectEnabled = false; - pbConnectVisible = false; - rbModeEnabled = false; - break; - case Vpn::ConnectionState::Reconnecting: - pbConnectChecked = true; - pbConnectEnabled = true; - pbConnectVisible = false; - rbModeEnabled = false; - break; - case Vpn::ConnectionState::Error: - pbConnectChecked = false; - pbConnectEnabled = true; - pbConnectVisible = true; - rbModeEnabled = true; - break; - case Vpn::ConnectionState::Unknown: - pbConnectChecked = false; - pbConnectEnabled = true; - pbConnectVisible = true; - rbModeEnabled = true; - } - - set_pushButtonConnectEnabled(pbConnectEnabled); - set_pushButtonConnectChecked(pbConnectChecked); - - set_pushButtonConnectVisible(pbConnectVisible); - set_widgetVpnModeEnabled(rbModeEnabled); -} - -void VpnLogic::onVpnProtocolError(ErrorCode errorCode) -{ - set_labelErrorText(errorString(errorCode)); -} - -void VpnLogic::onPushButtonConnectClicked() -{ - if (! pushButtonConnectChecked()) { - onConnect(); - } else { - onDisconnect(); - } -} - -void VpnLogic::onConnect() -{ - int serverIndex = m_settings->defaultServerIndex(); - ServerCredentials credentials = m_settings->serverCredentials(serverIndex); - DockerContainer container = m_settings->defaultContainer(serverIndex); - - if (m_settings->containers(serverIndex).isEmpty()) { - set_labelErrorText(tr("VPN Protocols is not installed.\n Please install VPN container at first")); - set_pushButtonConnectChecked(false); - return; - } - - if (container == DockerContainer::None) { - set_labelErrorText(tr("VPN Protocol not chosen")); - set_pushButtonConnectChecked(false); - return; - } - - - const QJsonObject &containerConfig = m_settings->containerConfig(serverIndex, container); - onConnectWorker(serverIndex, credentials, container, containerConfig); -} - -void VpnLogic::onConnectWorker(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig) -{ - set_labelErrorText(""); - set_pushButtonConnectChecked(true); - set_pushButtonConnectEnabled(false); - - qApp->processEvents(); - - emit connectToVpn(serverIndex, credentials, container, containerConfig); -} - -void VpnLogic::onDisconnect() -{ - onConnectionStateChanged(Vpn::ConnectionState::Disconnected); - emit disconnectFromVpn(); -} diff --git a/client/ui/pages_logic/VpnLogic.h b/client/ui/pages_logic/VpnLogic.h deleted file mode 100644 index a0f7763b..00000000 --- a/client/ui/pages_logic/VpnLogic.h +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef VPN_LOGIC_H -#define VPN_LOGIC_H - -#include "PageLogicBase.h" -#include "protocols/vpnprotocol.h" - -class UiLogic; - -class VpnLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(bool, pushButtonConnectChecked) - AUTO_PROPERTY(QString, labelSpeedReceivedText) - AUTO_PROPERTY(QString, labelSpeedSentText) - AUTO_PROPERTY(QString, labelStateText) - AUTO_PROPERTY(QString, labelCurrentServer) - AUTO_PROPERTY(QString, labelCurrentService) - AUTO_PROPERTY(QString, labelCurrentDns) - AUTO_PROPERTY(bool, amneziaDnsEnabled) - - AUTO_PROPERTY(bool, pushButtonConnectEnabled) - AUTO_PROPERTY(bool, pushButtonConnectVisible) - AUTO_PROPERTY(bool, widgetVpnModeEnabled) - AUTO_PROPERTY(bool, isContainerSupportedByCurrentPlatform) - AUTO_PROPERTY(bool, isContainerHaveAuthData) - - AUTO_PROPERTY(QString, labelErrorText) - AUTO_PROPERTY(QString, labelVersionText) - - AUTO_PROPERTY(bool, isCustomRoutesSupported) - - AUTO_PROPERTY(bool, radioButtonVpnModeAllSitesChecked) - AUTO_PROPERTY(bool, radioButtonVpnModeForwardSitesChecked) - AUTO_PROPERTY(bool, radioButtonVpnModeExceptSitesChecked) - - AUTO_PROPERTY(bool, labelLogEnabledVisible) - -public: - Q_INVOKABLE void onUpdatePage() override; - - Q_INVOKABLE void onRadioButtonVpnModeAllSitesClicked(); - Q_INVOKABLE void onRadioButtonVpnModeForwardSitesClicked(); - Q_INVOKABLE void onRadioButtonVpnModeExceptSitesClicked(); - - Q_INVOKABLE void onPushButtonConnectClicked(); - -public: - explicit VpnLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~VpnLogic() = default; - - bool getPushButtonConnectChecked() const; - void setPushButtonConnectChecked(bool pushButtonConnectChecked); - -public slots: - void onConnect(); - void onConnectWorker(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig); - void onDisconnect(); - - void onBytesChanged(quint64 receivedBytes, quint64 sentBytes); - void onConnectionStateChanged(Vpn::ConnectionState state); - void onVpnProtocolError(amnezia::ErrorCode errorCode); - -signals: - void connectToVpn(int serverIndex, - const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig); - - void disconnectFromVpn(); -}; -#endif // VPN_LOGIC_H diff --git a/client/ui/pages_logic/WizardLogic.cpp b/client/ui/pages_logic/WizardLogic.cpp deleted file mode 100644 index 23a20aed..00000000 --- a/client/ui/pages_logic/WizardLogic.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#include "WizardLogic.h" -#include "../uilogic.h" - -WizardLogic::WizardLogic(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent), - m_radioButtonHighChecked{false}, - m_radioButtonMediumChecked{true}, - m_radioButtonLowChecked{false}, - m_lineEditHighWebsiteMaskingText{}, - m_checkBoxVpnModeChecked{false} -{ - -} - -void WizardLogic::onUpdatePage() -{ - set_lineEditHighWebsiteMaskingText(protocols::cloak::defaultRedirSite); - set_radioButtonMediumChecked(true); -} - -QPair WizardLogic::getInstallConfigsFromWizardPage() const -{ - QJsonObject cloakConfig { - { config_key::container, ContainerProps::containerToString(DockerContainer::Cloak) }, - { ProtocolProps::protoToString(Proto::Cloak), QJsonObject { - { config_key::site, lineEditHighWebsiteMaskingText() }} - } - }; - QJsonObject ssConfig { - { config_key::container, ContainerProps::containerToString(DockerContainer::ShadowSocks) } - }; - QJsonObject openVpnConfig { - { config_key::container, ContainerProps::containerToString(DockerContainer::OpenVpn) } - }; - - QPair container; - - DockerContainer dockerContainer; - - if (radioButtonHighChecked()) { - container = {DockerContainer::Cloak, cloakConfig}; - } - - if (radioButtonMediumChecked()) { - container = {DockerContainer::ShadowSocks, ssConfig}; - } - - if (radioButtonLowChecked()) { - container = {DockerContainer::OpenVpn, openVpnConfig}; - } - - return container; -} - -void WizardLogic::onPushButtonVpnModeFinishClicked() -{ - auto container = getInstallConfigsFromWizardPage(); - uiLogic()->installServer(container); - if (checkBoxVpnModeChecked()) { - m_settings->setRouteMode(Settings::VpnOnlyForwardSites); - } else { - m_settings->setRouteMode(Settings::VpnAllSites); - } -} - -void WizardLogic::onPushButtonLowFinishClicked() -{ - auto container = getInstallConfigsFromWizardPage(); - uiLogic()->installServer(container); -} diff --git a/client/ui/pages_logic/WizardLogic.h b/client/ui/pages_logic/WizardLogic.h deleted file mode 100644 index a2e45af7..00000000 --- a/client/ui/pages_logic/WizardLogic.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef WIZARD_LOGIC_H -#define WIZARD_LOGIC_H - -#include "PageLogicBase.h" -#include "containers/containers_defs.h" - -class UiLogic; - -class WizardLogic : public PageLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(bool, radioButtonHighChecked) - AUTO_PROPERTY(bool, radioButtonMediumChecked) - AUTO_PROPERTY(bool, radioButtonLowChecked) - AUTO_PROPERTY(bool, checkBoxVpnModeChecked) - AUTO_PROPERTY(QString, lineEditHighWebsiteMaskingText) - -public: - Q_INVOKABLE void onUpdatePage() override; - Q_INVOKABLE void onPushButtonVpnModeFinishClicked(); - Q_INVOKABLE void onPushButtonLowFinishClicked(); - -public: - explicit WizardLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~WizardLogic() = default; - - QPair getInstallConfigsFromWizardPage() const; - -}; -#endif // WIZARD_LOGIC_H diff --git a/client/ui/pages_logic/protocols/CloakLogic.cpp b/client/ui/pages_logic/protocols/CloakLogic.cpp deleted file mode 100644 index 0062d12d..00000000 --- a/client/ui/pages_logic/protocols/CloakLogic.cpp +++ /dev/null @@ -1,137 +0,0 @@ -#include "CloakLogic.h" - -#include - -#include "core/servercontroller.h" -#include "ui/uilogic.h" -#include "ui/pages_logic/ServerConfiguringProgressLogic.h" - -using namespace amnezia; -using namespace PageEnumNS; - -CloakLogic::CloakLogic(UiLogic *logic, QObject *parent): - PageProtocolLogicBase(logic, parent), - m_comboBoxCipherText{"chacha20-poly1305"}, - m_lineEditSiteText{"tile.openstreetmap.org"}, - m_lineEditPortText{}, - m_pushButtonSaveVisible{false}, - m_progressBarResetVisible{false}, - m_lineEditPortEnabled{false}, - m_pageEnabled{true}, - m_labelInfoVisible{true}, - m_labelInfoText{}, - m_progressBarResetValue{0}, - m_progressBarResetMaximum{100} -{ - -} - -void CloakLogic::updateProtocolPage(const QJsonObject &ckConfig, DockerContainer container, bool haveAuthData) -{ - set_pageEnabled(haveAuthData); - set_pushButtonSaveVisible(haveAuthData); - set_progressBarResetVisible(haveAuthData); - - set_comboBoxCipherText(ckConfig.value(config_key::cipher). - toString(protocols::cloak::defaultCipher)); - - set_lineEditSiteText(ckConfig.value(config_key::site). - toString(protocols::cloak::defaultRedirSite)); - - set_lineEditPortText(ckConfig.value(config_key::port). - toString(protocols::cloak::defaultPort)); - - set_lineEditPortEnabled(container == DockerContainer::Cloak); -} - -QJsonObject CloakLogic::getProtocolConfigFromPage(QJsonObject oldConfig) -{ - oldConfig.insert(config_key::cipher, comboBoxCipherText()); - oldConfig.insert(config_key::site, lineEditSiteText()); - oldConfig.insert(config_key::port, lineEditPortText()); - - return oldConfig; -} - -void CloakLogic::onPushButtonSaveClicked() -{ - QJsonObject protocolConfig = m_settings->protocolConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer, Proto::Cloak); - protocolConfig = getProtocolConfigFromPage(protocolConfig); - - QJsonObject containerConfig = m_settings->containerConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer); - QJsonObject newContainerConfig = containerConfig; - newContainerConfig.insert(ProtocolProps::protoToString(Proto::Cloak), protocolConfig); - - ServerConfiguringProgressLogic::PageFunc pageFunc; - pageFunc.setEnabledFunc = [this] (bool enabled) -> void { - set_pageEnabled(enabled); - }; - ServerConfiguringProgressLogic::ButtonFunc saveButtonFunc; - saveButtonFunc.setVisibleFunc = [this] (bool visible) -> void { - set_pushButtonSaveVisible(visible); - }; - ServerConfiguringProgressLogic::LabelFunc waitInfoFunc; - waitInfoFunc.setVisibleFunc = [this] (bool visible) -> void { - set_labelInfoVisible(visible); - }; - waitInfoFunc.setTextFunc = [this] (const QString& text) -> void { - set_labelInfoText(text); - }; - ServerConfiguringProgressLogic::ProgressFunc progressBarFunc; - progressBarFunc.setVisibleFunc = [this] (bool visible) -> void { - set_progressBarResetVisible(visible); - }; - progressBarFunc.setValueFunc = [this] (int value) -> void { - set_progressBarResetValue(value); - }; - progressBarFunc.getValueFunc = [this] (void) -> int { - return progressBarResetValue(); - }; - progressBarFunc.getMaximumFunc = [this] (void) -> int { - return progressBarResetMaximum(); - }; - progressBarFunc.setTextVisibleFunc = [this] (bool visible) -> void { - set_progressBarTextVisible(visible); - }; - progressBarFunc.setTextFunc = [this] (const QString& text) -> void { - set_progressBarText(text); - }; - - ServerConfiguringProgressLogic::LabelFunc busyInfoFuncy; - busyInfoFuncy.setTextFunc = [this] (const QString& text) -> void { - set_labelServerBusyText(text); - }; - busyInfoFuncy.setVisibleFunc = [this] (bool visible) -> void { - set_labelServerBusyVisible(visible); - }; - - ServerConfiguringProgressLogic::ButtonFunc cancelButtonFunc; - cancelButtonFunc.setVisibleFunc = [this] (bool visible) -> void { - set_pushButtonCancelVisible(visible); - }; - - progressBarFunc.setTextVisibleFunc(true); - progressBarFunc.setTextFunc(QString("Configuring...")); - - auto installAction = [this, containerConfig, &newContainerConfig]() { - ServerController serverController(m_settings); - return serverController.updateContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), - uiLogic()->m_selectedDockerContainer, containerConfig, newContainerConfig); - }; - - ErrorCode e = uiLogic()->pageLogic()->doInstallAction(installAction, pageFunc, progressBarFunc, - saveButtonFunc, waitInfoFunc, - busyInfoFuncy, cancelButtonFunc); - - if (!e) { - m_settings->setContainerConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer, newContainerConfig); - m_settings->clearLastConnectionConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer); - } - - qDebug() << "Protocol saved with code:" << e << "for" << uiLogic()->m_selectedServerIndex << uiLogic()->m_selectedDockerContainer; -} - -void CloakLogic::onPushButtonCancelClicked() -{ - emit uiLogic()->pageLogic()->cancelDoInstallAction(true); -} diff --git a/client/ui/pages_logic/protocols/CloakLogic.h b/client/ui/pages_logic/protocols/CloakLogic.h deleted file mode 100644 index c135a621..00000000 --- a/client/ui/pages_logic/protocols/CloakLogic.h +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef CLOAK_LOGIC_H -#define CLOAK_LOGIC_H - -#include "PageProtocolLogicBase.h" - -class UiLogic; - -class CloakLogic : public PageProtocolLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(QString, comboBoxCipherText) - AUTO_PROPERTY(QString, lineEditSiteText) - AUTO_PROPERTY(QString, lineEditPortText) - AUTO_PROPERTY(bool, pushButtonSaveVisible) - AUTO_PROPERTY(bool, progressBarResetVisible) - AUTO_PROPERTY(bool, lineEditPortEnabled) - AUTO_PROPERTY(bool, pageEnabled) - AUTO_PROPERTY(bool, labelInfoVisible) - AUTO_PROPERTY(QString, labelInfoText) - AUTO_PROPERTY(int, progressBarResetValue) - AUTO_PROPERTY(int, progressBarResetMaximum) - AUTO_PROPERTY(bool, progressBarTextVisible) - AUTO_PROPERTY(QString, progressBarText) - - AUTO_PROPERTY(bool, labelServerBusyVisible) - AUTO_PROPERTY(QString, labelServerBusyText) - - AUTO_PROPERTY(bool, pushButtonCancelVisible) - -public: - Q_INVOKABLE void onPushButtonSaveClicked(); - Q_INVOKABLE void onPushButtonCancelClicked(); - -public: - explicit CloakLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~CloakLogic() = default; - - void updateProtocolPage(const QJsonObject &ckConfig, DockerContainer container, bool haveAuthData) override; - QJsonObject getProtocolConfigFromPage(QJsonObject oldConfig) override; - -private: - UiLogic *m_uiLogic; - -}; -#endif // CLOAK_LOGIC_H diff --git a/client/ui/pages_logic/protocols/OpenVpnLogic.cpp b/client/ui/pages_logic/protocols/OpenVpnLogic.cpp deleted file mode 100644 index 2eb68ed9..00000000 --- a/client/ui/pages_logic/protocols/OpenVpnLogic.cpp +++ /dev/null @@ -1,203 +0,0 @@ -#include "OpenVpnLogic.h" - -#include - -#include "core/servercontroller.h" -#include "ui/uilogic.h" -#include "ui/pages_logic/ServerConfiguringProgressLogic.h" - -using namespace amnezia; -using namespace PageEnumNS; - -OpenVpnLogic::OpenVpnLogic(UiLogic *logic, QObject *parent): - PageProtocolLogicBase(logic, parent), - m_lineEditSubnetText{""}, - - m_radioButtonTcpEnabled{true}, - m_radioButtonTcpChecked{false}, - m_radioButtonUdpEnabled{true}, - m_radioButtonUdpChecked{false}, - - m_checkBoxAutoEncryptionChecked{}, - m_comboBoxVpnCipherText{"AES-256-GCM"}, - m_comboBoxVpnHashText{"SHA512"}, - m_checkBoxBlockDnsChecked{false}, - m_lineEditPortText{}, - m_checkBoxTlsAuthChecked{false}, - m_textAreaAdditionalClientConfig{""}, - m_textAreaAdditionalServerConfig{""}, - m_pushButtonSaveVisible{false}, - m_progressBarResetVisible{false}, - - m_lineEditPortEnabled{false}, - m_labelProtoOpenVpnInfoVisible{true}, - m_labelProtoOpenVpnInfoText{}, - m_progressBarResetValue{0}, - m_progressBarResetMaximum{100} -{ - -} - -void OpenVpnLogic::updateProtocolPage(const QJsonObject &openvpnConfig, DockerContainer container, bool haveAuthData) -{ - qDebug() << "OpenVpnLogic::updateProtocolPage"; - set_pageEnabled(haveAuthData); - set_pushButtonSaveVisible(haveAuthData); - set_progressBarResetVisible(haveAuthData); - - set_radioButtonUdpEnabled(true); - set_radioButtonTcpEnabled(true); - - set_lineEditSubnetText(openvpnConfig.value(config_key::subnet_address). - toString(protocols::openvpn::defaultSubnetAddress)); - - QString transport; - if (container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) { - transport = "tcp"; - set_radioButtonUdpEnabled(false); - set_radioButtonTcpEnabled(false); - } else { - transport = openvpnConfig.value(config_key::transport_proto). - toString(protocols::openvpn::defaultTransportProto); - } - set_radioButtonUdpChecked(transport == protocols::openvpn::defaultTransportProto); - set_radioButtonTcpChecked(transport != protocols::openvpn::defaultTransportProto); - - set_comboBoxVpnCipherText(openvpnConfig.value(config_key::cipher). - toString(protocols::openvpn::defaultCipher)); - - set_comboBoxVpnHashText(openvpnConfig.value(config_key::hash). - toString(protocols::openvpn::defaultHash)); - - bool blockOutsideDns = openvpnConfig.value(config_key::block_outside_dns).toBool(protocols::openvpn::defaultBlockOutsideDns); - set_checkBoxBlockDnsChecked(blockOutsideDns); - - bool isNcpDisabled = openvpnConfig.value(config_key::ncp_disable).toBool(protocols::openvpn::defaultNcpDisable); - set_checkBoxAutoEncryptionChecked(!isNcpDisabled); - - bool isTlsAuth = openvpnConfig.value(config_key::tls_auth).toBool(protocols::openvpn::defaultTlsAuth); - set_checkBoxTlsAuthChecked(isTlsAuth); - - QString additionalClientConfig = openvpnConfig.value(config_key::additional_client_config). - toString(protocols::openvpn::defaultAdditionalClientConfig); - set_textAreaAdditionalClientConfig(additionalClientConfig); - - QString additionalServerConfig = openvpnConfig.value(config_key::additional_server_config). - toString(protocols::openvpn::defaultAdditionalServerConfig); - set_textAreaAdditionalServerConfig(additionalServerConfig); - - set_lineEditPortText(openvpnConfig.value(config_key::port). - toString(protocols::openvpn::defaultPort)); - - set_lineEditPortEnabled(container == DockerContainer::OpenVpn); - - auto lastConfig = openvpnConfig.value(config_key::last_config).toString(); - auto lastConfigJson = QJsonDocument::fromJson(lastConfig.toUtf8()).object(); - QStringList lines = lastConfigJson.value(config_key::config).toString().replace("\r", "").split("\n"); - QString openVpnLastConfigText; - for (const QString &l: lines) { - openVpnLastConfigText.append(l + "\n"); - } - - set_openVpnLastConfigText(openVpnLastConfigText); - set_isThirdPartyConfig(openvpnConfig.value(config_key::isThirdPartyConfig).isBool()); -} - -void OpenVpnLogic::onPushButtonSaveClicked() -{ - QJsonObject protocolConfig = m_settings->protocolConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer, Proto::OpenVpn); - protocolConfig = getProtocolConfigFromPage(protocolConfig); - - QJsonObject containerConfig = m_settings->containerConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer); - QJsonObject newContainerConfig = containerConfig; - newContainerConfig.insert(ProtocolProps::protoToString(Proto::OpenVpn), protocolConfig); - - ServerConfiguringProgressLogic::PageFunc pageFunc; - pageFunc.setEnabledFunc = [this] (bool enabled) -> void { - set_pageEnabled(enabled); - }; - ServerConfiguringProgressLogic::ButtonFunc saveButtonFunc; - saveButtonFunc.setVisibleFunc = [this] (bool visible) -> void { - set_pushButtonSaveVisible(visible); - }; - ServerConfiguringProgressLogic::LabelFunc waitInfoFunc; - waitInfoFunc.setVisibleFunc = [this] (bool visible) -> void { - set_labelProtoOpenVpnInfoVisible(visible); - }; - waitInfoFunc.setTextFunc = [this] (const QString& text) -> void { - set_labelProtoOpenVpnInfoText(text); - }; - ServerConfiguringProgressLogic::ProgressFunc progressBarFunc; - progressBarFunc.setVisibleFunc = [this] (bool visible) -> void { - set_progressBarResetVisible(visible); - }; - progressBarFunc.setValueFunc = [this] (int value) -> void { - set_progressBarResetValue(value); - }; - progressBarFunc.getValueFunc = [this] (void) -> int { - return progressBarResetValue(); - }; - progressBarFunc.getMaximumFunc = [this] (void) -> int { - return progressBarResetMaximum(); - }; - progressBarFunc.setTextVisibleFunc = [this] (bool visible) -> void { - set_progressBarTextVisible(visible); - }; - progressBarFunc.setTextFunc = [this] (const QString& text) -> void { - set_progressBarText(text); - }; - - ServerConfiguringProgressLogic::LabelFunc busyInfoFuncy; - busyInfoFuncy.setTextFunc = [this] (const QString& text) -> void { - set_labelServerBusyText(text); - }; - busyInfoFuncy.setVisibleFunc = [this] (bool visible) -> void { - set_labelServerBusyVisible(visible); - }; - - ServerConfiguringProgressLogic::ButtonFunc cancelButtonFunc; - cancelButtonFunc.setVisibleFunc = [this] (bool visible) -> void { - set_pushButtonCancelVisible(visible); - }; - - progressBarFunc.setTextVisibleFunc(true); - progressBarFunc.setTextFunc(QString("Configuring...")); - - auto installAction = [this, containerConfig, &newContainerConfig]() { - ServerController serverController(m_settings); - return serverController.updateContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), - uiLogic()->m_selectedDockerContainer, containerConfig, newContainerConfig); - }; - - ErrorCode e = uiLogic()->pageLogic()->doInstallAction(installAction, pageFunc, progressBarFunc, - saveButtonFunc, waitInfoFunc, - busyInfoFuncy, cancelButtonFunc); - - if (!e) { - m_settings->setContainerConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer, newContainerConfig); - m_settings->clearLastConnectionConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer); - } - qDebug() << "Protocol saved with code:" << e << "for" << uiLogic()->m_selectedServerIndex << uiLogic()->m_selectedDockerContainer; -} - -QJsonObject OpenVpnLogic::getProtocolConfigFromPage(QJsonObject oldConfig) -{ - oldConfig.insert(config_key::subnet_address, lineEditSubnetText()); - oldConfig.insert(config_key::transport_proto, - ProtocolProps::transportProtoToString(radioButtonUdpChecked() ? ProtocolEnumNS::Udp : ProtocolEnumNS::Tcp)); - - oldConfig.insert(config_key::ncp_disable, ! checkBoxAutoEncryptionChecked()); - oldConfig.insert(config_key::cipher, comboBoxVpnCipherText()); - oldConfig.insert(config_key::hash, comboBoxVpnHashText()); - oldConfig.insert(config_key::block_outside_dns, checkBoxBlockDnsChecked()); - oldConfig.insert(config_key::port, lineEditPortText()); - oldConfig.insert(config_key::tls_auth, checkBoxTlsAuthChecked()); - oldConfig.insert(config_key::additional_client_config, textAreaAdditionalClientConfig()); - oldConfig.insert(config_key::additional_server_config, textAreaAdditionalServerConfig()); - return oldConfig; -} - -void OpenVpnLogic::onPushButtonCancelClicked() -{ - emit uiLogic()->pageLogic()->cancelDoInstallAction(true); -} diff --git a/client/ui/pages_logic/protocols/OpenVpnLogic.h b/client/ui/pages_logic/protocols/OpenVpnLogic.h deleted file mode 100644 index db7d3baf..00000000 --- a/client/ui/pages_logic/protocols/OpenVpnLogic.h +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef OPENVPN_LOGIC_H -#define OPENVPN_LOGIC_H - -#include "PageProtocolLogicBase.h" - -class UiLogic; - -class OpenVpnLogic : public PageProtocolLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(QString, lineEditSubnetText) - - AUTO_PROPERTY(bool, radioButtonTcpEnabled) - AUTO_PROPERTY(bool, radioButtonUdpEnabled) - AUTO_PROPERTY(bool, radioButtonTcpChecked) - AUTO_PROPERTY(bool, radioButtonUdpChecked) - - AUTO_PROPERTY(bool, checkBoxAutoEncryptionChecked) - AUTO_PROPERTY(QString, comboBoxVpnCipherText) - AUTO_PROPERTY(QString, comboBoxVpnHashText) - AUTO_PROPERTY(bool, checkBoxBlockDnsChecked) - AUTO_PROPERTY(QString, lineEditPortText) - AUTO_PROPERTY(bool, checkBoxTlsAuthChecked) - AUTO_PROPERTY(QString, textAreaAdditionalClientConfig) - AUTO_PROPERTY(QString, textAreaAdditionalServerConfig) - - AUTO_PROPERTY(bool, pushButtonSaveVisible) - AUTO_PROPERTY(bool, progressBarResetVisible) - - AUTO_PROPERTY(bool, lineEditPortEnabled) - - AUTO_PROPERTY(bool, labelProtoOpenVpnInfoVisible) - AUTO_PROPERTY(QString, labelProtoOpenVpnInfoText) - AUTO_PROPERTY(int, progressBarResetValue) - AUTO_PROPERTY(int, progressBarResetMaximum) - AUTO_PROPERTY(bool, progressBarTextVisible) - AUTO_PROPERTY(QString, progressBarText) - - AUTO_PROPERTY(bool, labelServerBusyVisible) - AUTO_PROPERTY(QString, labelServerBusyText) - - AUTO_PROPERTY(bool, pushButtonCancelVisible) - - AUTO_PROPERTY(QString, openVpnLastConfigText) - AUTO_PROPERTY(bool, isThirdPartyConfig) - -public: - Q_INVOKABLE void onPushButtonSaveClicked(); - Q_INVOKABLE void onPushButtonCancelClicked(); - -public: - explicit OpenVpnLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~OpenVpnLogic() = default; - - void updateProtocolPage(const QJsonObject &openvpnConfig, DockerContainer container, bool haveAuthData) override; - QJsonObject getProtocolConfigFromPage(QJsonObject oldConfig) override; - -private: - UiLogic *m_uiLogic; - -}; -#endif // OPENVPN_LOGIC_H diff --git a/client/ui/pages_logic/protocols/OtherProtocolsLogic.cpp b/client/ui/pages_logic/protocols/OtherProtocolsLogic.cpp deleted file mode 100644 index 965a3baf..00000000 --- a/client/ui/pages_logic/protocols/OtherProtocolsLogic.cpp +++ /dev/null @@ -1,169 +0,0 @@ -#include -#include -#include -#include -#include - -#include "OtherProtocolsLogic.h" -#include -#include "../../uilogic.h" -#include "utilities.h" - -#ifdef Q_OS_WINDOWS -#include -#endif - -using namespace amnezia; -using namespace PageEnumNS; - -OtherProtocolsLogic::OtherProtocolsLogic(UiLogic *logic, QObject *parent): - PageProtocolLogicBase(logic, parent), - m_checkBoxSftpRestoreChecked{false} - -{ - -} - -OtherProtocolsLogic::~OtherProtocolsLogic() -{ -#ifdef Q_OS_WINDOWS - for (QProcess *p: m_sftpMountProcesses) { - if (p) Utils::signalCtrl(p->processId(), CTRL_C_EVENT); - if (p) p->kill(); - if (p) p->waitForFinished(); - if (p) delete p; - } -#endif -} - -void OtherProtocolsLogic::updateProtocolPage(const QJsonObject &config, DockerContainer container, bool haveAuthData) -{ - set_labelTftpUserNameText(config.value(config_key::userName).toString()); - set_labelTftpPasswordText(config.value(config_key::password).toString(protocols::sftp::defaultUserName)); - set_labelTftpPortText(config.value(config_key::port).toString()); - - set_labelTorWebSiteAddressText(config.value(config_key::site).toString()); - set_pushButtonSftpMountEnabled(true); -} - -#ifdef Q_OS_WINDOWS -QString OtherProtocolsLogic::getNextDriverLetter() const -{ - QProcess drivesProc; - drivesProc.start("wmic logicaldisk get caption"); - drivesProc.waitForFinished(); - QString drives = drivesProc.readAll(); - qDebug() << drives; - - - QString letters = "CFGHIJKLMNOPQRSTUVWXYZ"; - QString letter; - for (int i = letters.size() - 1; i > 0; i--) { - letter = letters.at(i); - if (!drives.contains(letter + ":")) break; - } - if (letter == "C:") { - // set err info - qDebug() << "Can't find free drive letter"; - return ""; - } - return letter; -} -#endif - -//QJsonObject OtherProtocolsLogic::getProtocolConfigFromPage(QJsonObject oldConfig) -//{ - -//} - - -void OtherProtocolsLogic::onPushButtonSftpMountDriveClicked() -{ - QString mountPath; - QString cmd; - QString host = m_settings->serverCredentials(uiLogic()->m_selectedServerIndex).hostName; - - -#ifdef Q_OS_WINDOWS - mountPath = getNextDriverLetter() + ":"; - // QString cmd = QString("net use \\\\sshfs\\%1@x.x.x.x!%2 /USER:%1 %3") - // .arg(labelTftpUserNameText()) - // .arg(labelTftpPortText()) - // .arg(labelTftpPasswordText()); - - cmd = "C:\\Program Files\\SSHFS-Win\\bin\\sshfs.exe"; -#elif defined AMNEZIA_DESKTOP - mountPath = QString("%1/sftp:%2:%3") - .arg(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)) - .arg(host) - .arg(labelTftpPortText()); - QDir dir(mountPath); - if (!dir.exists()){ - dir.mkpath(mountPath); - } - - cmd = "/usr/local/bin/sshfs"; -#endif - -#ifdef AMNEZIA_DESKTOP - set_pushButtonSftpMountEnabled(false); - QProcess *p = new QProcess; - m_sftpMountProcesses.append(p); - p->setProcessChannelMode(QProcess::MergedChannels); - - connect(p, &QProcess::readyRead, this, [this, p, mountPath](){ - QString s = p->readAll(); - if (s.contains("The service sshfs has been started")) { - QDesktopServices::openUrl(QUrl("file:///" + mountPath)); - set_pushButtonSftpMountEnabled(true); - } - qDebug() << s; - }); - - - - p->setProgram(cmd); - - QString args = QString( - "%1@%2:/ %3 " - "-o port=%4 " - "-f " - "-o reconnect " - "-o rellinks " - "-o fstypename=SSHFS " - "-o ssh_command=/usr/bin/ssh.exe " - "-o UserKnownHostsFile=/dev/null " - "-o StrictHostKeyChecking=no " - "-o password_stdin") - .arg(labelTftpUserNameText()) - .arg(host) - .arg(mountPath) - .arg(labelTftpPortText()); - - -// args.replace("\n", " "); -// args.replace("\r", " "); -//#ifndef Q_OS_WIN -// args.replace("reconnect-orellinks", ""); -//#endif - p->setArguments(args.split(" ", Qt::SkipEmptyParts)); - p->start(); - p->waitForStarted(50); - if (p->state() != QProcess::Running) { - qDebug() << "onPushButtonSftpMountDriveClicked process not started"; - qDebug() << args; - } - else { - p->write((labelTftpPasswordText() + "\n").toUtf8()); - } - - //qDebug().noquote() << "onPushButtonSftpMountDriveClicked" << args; - - set_pushButtonSftpMountEnabled(true); -#endif -} - -void OtherProtocolsLogic::checkBoxSftpRestoreClicked() -{ - -} diff --git a/client/ui/pages_logic/protocols/OtherProtocolsLogic.h b/client/ui/pages_logic/protocols/OtherProtocolsLogic.h deleted file mode 100644 index 508c2914..00000000 --- a/client/ui/pages_logic/protocols/OtherProtocolsLogic.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef OTHER_PROTOCOLS_LOGIC_H -#define OTHER_PROTOCOLS_LOGIC_H - -#include "PageProtocolLogicBase.h" - -#include - -class UiLogic; - -class OtherProtocolsLogic : public PageProtocolLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(QString, labelTftpUserNameText) - AUTO_PROPERTY(QString, labelTftpPasswordText) - AUTO_PROPERTY(QString, labelTftpPortText) - AUTO_PROPERTY(bool, pushButtonSftpMountEnabled) - AUTO_PROPERTY(bool, checkBoxSftpRestoreChecked) - - AUTO_PROPERTY(QString, labelTorWebSiteAddressText) - - -public: - Q_INVOKABLE void onPushButtonSftpMountDriveClicked(); - Q_INVOKABLE void checkBoxSftpRestoreClicked(); -public: - explicit OtherProtocolsLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~OtherProtocolsLogic(); - - void updateProtocolPage(const QJsonObject &config, DockerContainer container, bool haveAuthData) override; - //QJsonObject getProtocolConfigFromPage(QJsonObject oldConfig) override; - -private: - UiLogic *m_uiLogic; - -#ifdef AMNEZIA_DESKTOP - QList m_sftpMountProcesses; -#endif - -#ifdef Q_OS_WINDOWS - QString getNextDriverLetter() const; -#endif - -}; -#endif // OTHER_PROTOCOLS_LOGIC_H diff --git a/client/ui/pages_logic/protocols/PageProtocolLogicBase.cpp b/client/ui/pages_logic/protocols/PageProtocolLogicBase.cpp deleted file mode 100644 index 62c5aa89..00000000 --- a/client/ui/pages_logic/protocols/PageProtocolLogicBase.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "PageProtocolLogicBase.h" - - -PageProtocolLogicBase::PageProtocolLogicBase(UiLogic *logic, QObject *parent): - PageLogicBase(logic, parent) -{ - -} diff --git a/client/ui/pages_logic/protocols/PageProtocolLogicBase.h b/client/ui/pages_logic/protocols/PageProtocolLogicBase.h deleted file mode 100644 index ab66f8a9..00000000 --- a/client/ui/pages_logic/protocols/PageProtocolLogicBase.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef PAGE_PROTOCOL_LOGIC_BASE_H -#define PAGE_PROTOCOL_LOGIC_BASE_H - -#include "settings.h" -#include "../PageLogicBase.h" - -using namespace amnezia; -using namespace PageEnumNS; - -class UiLogic; - -class PageProtocolLogicBase : public PageLogicBase -{ - Q_OBJECT - -public: - explicit PageProtocolLogicBase(UiLogic *uiLogic, QObject *parent = nullptr); - ~PageProtocolLogicBase() = default; - - virtual void updateProtocolPage(const QJsonObject &config, DockerContainer container, bool haveAuthData) {} - virtual QJsonObject getProtocolConfigFromPage(QJsonObject oldConfig) { return QJsonObject(); } - -}; -#endif // PAGE_PROTOCOL_LOGIC_BASE_H diff --git a/client/ui/pages_logic/protocols/ShadowSocksLogic.cpp b/client/ui/pages_logic/protocols/ShadowSocksLogic.cpp deleted file mode 100644 index f9220a92..00000000 --- a/client/ui/pages_logic/protocols/ShadowSocksLogic.cpp +++ /dev/null @@ -1,127 +0,0 @@ -#include "ShadowSocksLogic.h" - -#include - -#include "core/servercontroller.h" -#include "ui/pages_logic/ServerConfiguringProgressLogic.h" -#include "ui/uilogic.h" - -using namespace amnezia; -using namespace PageEnumNS; - -ShadowSocksLogic::ShadowSocksLogic(UiLogic *logic, QObject *parent): - PageProtocolLogicBase(logic, parent), - m_comboBoxCipherText{"chacha20-poly1305"}, - m_lineEditPortText{}, - m_pushButtonSaveVisible{false}, - m_progressBarResetVisible{false}, - m_lineEditPortEnabled{false}, - m_labelInfoVisible{true}, - m_labelInfoText{}, - m_progressBarResetValue{0}, - m_progressBarResetMaximum{100} -{ - -} - -void ShadowSocksLogic::updateProtocolPage(const QJsonObject &ssConfig, DockerContainer container, bool haveAuthData) -{ - set_pageEnabled(haveAuthData); - set_pushButtonSaveVisible(haveAuthData); - set_progressBarResetVisible(haveAuthData); - - set_comboBoxCipherText(ssConfig.value(config_key::cipher). - toString(protocols::shadowsocks::defaultCipher)); - - set_lineEditPortText(ssConfig.value(config_key::port). - toString(protocols::shadowsocks::defaultPort)); - - set_lineEditPortEnabled(container == DockerContainer::ShadowSocks); -} - -QJsonObject ShadowSocksLogic::getProtocolConfigFromPage(QJsonObject oldConfig) -{ - oldConfig.insert(config_key::cipher, comboBoxCipherText()); - oldConfig.insert(config_key::port, lineEditPortText()); - - return oldConfig; -} - -void ShadowSocksLogic::onPushButtonSaveClicked() -{ - QJsonObject protocolConfig = m_settings->protocolConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer, Proto::ShadowSocks); - - QJsonObject containerConfig = m_settings->containerConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer); - QJsonObject newContainerConfig = containerConfig; - newContainerConfig.insert(ProtocolProps::protoToString(Proto::ShadowSocks), protocolConfig); - ServerConfiguringProgressLogic::PageFunc pageFunc; - pageFunc.setEnabledFunc = [this] (bool enabled) -> void { - set_pageEnabled(enabled); - }; - ServerConfiguringProgressLogic::ButtonFunc saveButtonFunc; - saveButtonFunc.setVisibleFunc = [this] (bool visible) -> void { - set_pushButtonSaveVisible(visible); - }; - ServerConfiguringProgressLogic::LabelFunc waitInfoFunc; - waitInfoFunc.setVisibleFunc = [this] (bool visible) -> void { - set_labelInfoVisible(visible); - }; - waitInfoFunc.setTextFunc = [this] (const QString& text) -> void { - set_labelInfoText(text); - }; - ServerConfiguringProgressLogic::ProgressFunc progressBarFunc; - progressBarFunc.setVisibleFunc = [this] (bool visible) -> void { - set_progressBarResetVisible(visible); - }; - progressBarFunc.setValueFunc = [this] (int value) -> void { - set_progressBarResetValue(value); - }; - progressBarFunc.getValueFunc = [this] (void) -> int { - return progressBarResetValue(); - }; - progressBarFunc.getMaximumFunc = [this] (void) -> int { - return progressBarResetMaximum(); - }; - progressBarFunc.setTextVisibleFunc = [this] (bool visible) -> void { - set_progressBarTextVisible(visible); - }; - progressBarFunc.setTextFunc = [this] (const QString& text) -> void { - set_progressBarText(text); - }; - - ServerConfiguringProgressLogic::LabelFunc busyInfoFuncy; - busyInfoFuncy.setTextFunc = [this] (const QString& text) -> void { - set_labelServerBusyText(text); - }; - busyInfoFuncy.setVisibleFunc = [this] (bool visible) -> void { - set_labelServerBusyVisible(visible); - }; - - ServerConfiguringProgressLogic::ButtonFunc cancelButtonFunc; - cancelButtonFunc.setVisibleFunc = [this] (bool visible) -> void { - set_pushButtonCancelVisible(visible); - }; - - progressBarFunc.setTextVisibleFunc(true); - progressBarFunc.setTextFunc(QString("Configuring...")); - - auto installAction = [this, containerConfig, &newContainerConfig]() { - ServerController serverController(m_settings); - return serverController.updateContainer(m_settings->serverCredentials(uiLogic()->m_selectedServerIndex), - uiLogic()->m_selectedDockerContainer, containerConfig, newContainerConfig); - }; - ErrorCode e = uiLogic()->pageLogic()->doInstallAction(installAction, pageFunc, progressBarFunc, - saveButtonFunc, waitInfoFunc, - busyInfoFuncy, cancelButtonFunc); - - if (!e) { - m_settings->setContainerConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer, newContainerConfig); - m_settings->clearLastConnectionConfig(uiLogic()->m_selectedServerIndex, uiLogic()->m_selectedDockerContainer); - } - qDebug() << "Protocol saved with code:" << e << "for" << uiLogic()->m_selectedServerIndex << uiLogic()->m_selectedDockerContainer; -} - -void ShadowSocksLogic::onPushButtonCancelClicked() -{ - emit uiLogic()->pageLogic()->cancelDoInstallAction(true); -} diff --git a/client/ui/pages_logic/protocols/ShadowSocksLogic.h b/client/ui/pages_logic/protocols/ShadowSocksLogic.h deleted file mode 100644 index bf926928..00000000 --- a/client/ui/pages_logic/protocols/ShadowSocksLogic.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef SHADOWSOCKS_LOGIC_H -#define SHADOWSOCKS_LOGIC_H - -#include "PageProtocolLogicBase.h" - -class UiLogic; - -class ShadowSocksLogic : public PageProtocolLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(QString, comboBoxCipherText) - AUTO_PROPERTY(QString, lineEditPortText) - AUTO_PROPERTY(bool, pushButtonSaveVisible) - AUTO_PROPERTY(bool, progressBarResetVisible) - AUTO_PROPERTY(bool, lineEditPortEnabled) - AUTO_PROPERTY(bool, labelInfoVisible) - AUTO_PROPERTY(QString, labelInfoText) - AUTO_PROPERTY(int, progressBarResetValue) - AUTO_PROPERTY(int, progressBarResetMaximum) - AUTO_PROPERTY(bool, progressBarTextVisible) - AUTO_PROPERTY(QString, progressBarText) - - AUTO_PROPERTY(bool, labelServerBusyVisible) - AUTO_PROPERTY(QString, labelServerBusyText) - - AUTO_PROPERTY(bool, pushButtonCancelVisible) - -public: - Q_INVOKABLE void onPushButtonSaveClicked(); - Q_INVOKABLE void onPushButtonCancelClicked(); - -public: - explicit ShadowSocksLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~ShadowSocksLogic() = default; - - void updateProtocolPage(const QJsonObject &ssConfig, DockerContainer container, bool haveAuthData) override; - QJsonObject getProtocolConfigFromPage(QJsonObject oldConfig) override; - -private: - UiLogic *m_uiLogic; - -}; -#endif // SHADOWSOCKS_LOGIC_H diff --git a/client/ui/pages_logic/protocols/WireGuardLogic.cpp b/client/ui/pages_logic/protocols/WireGuardLogic.cpp deleted file mode 100644 index a6c661f5..00000000 --- a/client/ui/pages_logic/protocols/WireGuardLogic.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include "WireGuardLogic.h" -#include "core/servercontroller.h" -#include -#include "../../uilogic.h" - -using namespace amnezia; -using namespace PageEnumNS; - -WireGuardLogic::WireGuardLogic(UiLogic *logic, QObject *parent): - PageProtocolLogicBase(logic, parent) -{ - -} - -void WireGuardLogic::updateProtocolPage(const QJsonObject &wireGuardConfig, DockerContainer container, bool haveAuthData) -{ - qDebug() << "WireGuardLogic::updateProtocolPage"; - - auto lastConfigJsonDoc = QJsonDocument::fromJson(wireGuardConfig.value(config_key::last_config).toString().toUtf8()); - auto lastConfigJson = lastConfigJsonDoc.object(); - - QString wireGuardLastConfigText; - QStringList lines = lastConfigJson.value(config_key::config).toString().replace("\r", "").split("\n"); - for (const QString &l: lines) { - wireGuardLastConfigText.append(l + "\n"); - } - - set_wireGuardLastConfigText(wireGuardLastConfigText); - set_isThirdPartyConfig(wireGuardConfig.value(config_key::isThirdPartyConfig).toBool()); -} diff --git a/client/ui/pages_logic/protocols/WireGuardLogic.h b/client/ui/pages_logic/protocols/WireGuardLogic.h deleted file mode 100644 index 0b3bfc7b..00000000 --- a/client/ui/pages_logic/protocols/WireGuardLogic.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef WIREGUARDLOGIC_H -#define WIREGUARDLOGIC_H - -#include "PageProtocolLogicBase.h" - -class UiLogic; - -class WireGuardLogic : public PageProtocolLogicBase -{ - Q_OBJECT - - AUTO_PROPERTY(QString, wireGuardLastConfigText) - AUTO_PROPERTY(bool, isThirdPartyConfig) - -public: - explicit WireGuardLogic(UiLogic *uiLogic, QObject *parent = nullptr); - ~WireGuardLogic() = default; - - void updateProtocolPage(const QJsonObject &wireGuardConfig, DockerContainer container, bool haveAuthData) override; - -private: - UiLogic *m_uiLogic; - -}; - -#endif // WIREGUARDLOGIC_H diff --git a/client/ui/qml/Controls/BackButton.qml b/client/ui/qml/Controls/BackButton.qml deleted file mode 100644 index 47f0970c..00000000 --- a/client/ui/qml/Controls/BackButton.qml +++ /dev/null @@ -1,33 +0,0 @@ -import QtQuick -import QtQuick.Controls - -Button { - id: root - x: 10 - y: 5 - width: 41 - height: 35 - - hoverEnabled: true - property bool containsMouse: hovered - - background: Item {} - - MouseArea { - id: mouseArea - anchors.fill: parent - enabled: false - cursorShape: Qt.PointingHandCursor - } - - onClicked: { - UiLogic.closePage() - } - - contentItem: Image { - id: img - source: "qrc:/images/arrow_left.png" - anchors.fill: root - anchors.margins: root.containsMouse ? 9 : 10 - } -} diff --git a/client/ui/qml/Controls/BasicButtonType.qml b/client/ui/qml/Controls/BasicButtonType.qml deleted file mode 100644 index e115df29..00000000 --- a/client/ui/qml/Controls/BasicButtonType.qml +++ /dev/null @@ -1,17 +0,0 @@ -import QtQuick -import QtQuick.Controls - -Button { - id: root - property bool containsMouse: hovered - hoverEnabled: true - flat: true - highlighted: false - - MouseArea { - id: mouseArea - anchors.fill: parent - enabled: false - cursorShape: Qt.PointingHandCursor - } -} diff --git a/client/ui/qml/Controls/BlueButtonType.qml b/client/ui/qml/Controls/BlueButtonType.qml deleted file mode 100644 index a3602c4a..00000000 --- a/client/ui/qml/Controls/BlueButtonType.qml +++ /dev/null @@ -1,28 +0,0 @@ -import QtQuick -import QtQuick.Controls - -import "../Config" - -BasicButtonType { - id: root - width: parent.width - 2 * GC.defaultMargin - implicitHeight: 40 - - background: Rectangle { - anchors.fill: parent - radius: 4 - color: root.enabled ? (root.containsMouse ? "#211966" : "#100A44") : "#888888" - } - font.pixelSize: 16 - contentItem: Text { - anchors.fill: parent - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: root.font.pixelSize - color: "#D4D4D4" - text: root.text - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - antialiasing: true -} diff --git a/client/ui/qml/Controls/Caption.qml b/client/ui/qml/Controls/Caption.qml deleted file mode 100644 index 50fc9aca..00000000 --- a/client/ui/qml/Controls/Caption.qml +++ /dev/null @@ -1,17 +0,0 @@ -import QtQuick -import QtQuick.Controls - -Text { - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 24 - color: "#100A44" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - x: 10 - y: 35 - width: parent.width - 40 - anchors.horizontalCenter: parent.horizontalCenter - wrapMode: Text.Wrap - //height: 31 -} diff --git a/client/ui/qml/Controls/CheckBoxType.qml b/client/ui/qml/Controls/CheckBoxType.qml deleted file mode 100644 index 0331706c..00000000 --- a/client/ui/qml/Controls/CheckBoxType.qml +++ /dev/null @@ -1,27 +0,0 @@ -import QtQuick -import QtQuick.Controls - -CheckBox { - id: root - property int imageWidth : 20 - property int imageHeight : 20 - indicator: Image { - id: indicator - anchors.verticalCenter: root.verticalCenter - height: imageHeight - width: imageWidth - source: root.checked ? "qrc:/images/controls/check_on.png" - : "qrc:/images/controls/check_off.png" - } - - contentItem: Text { - text: root.text - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#181922" - verticalAlignment: Text.AlignVCenter - leftPadding: root.indicator.width + root.spacing - wrapMode: Text.Wrap - } -} diff --git a/client/ui/qml/Controls/ComboBoxType.qml b/client/ui/qml/Controls/ComboBoxType.qml deleted file mode 100644 index 090ca9de..00000000 --- a/client/ui/qml/Controls/ComboBoxType.qml +++ /dev/null @@ -1,11 +0,0 @@ -import QtQuick -import QtQuick.Controls - -ComboBox { - id: root - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - - popup.font.pixelSize: 16 -} diff --git a/client/ui/qml/Controls/ContextMenu.qml b/client/ui/qml/Controls/ContextMenu.qml deleted file mode 100644 index 867fcb10..00000000 --- a/client/ui/qml/Controls/ContextMenu.qml +++ /dev/null @@ -1,33 +0,0 @@ -import QtQuick -import QtQuick.Controls -import Qt.labs.platform - -Menu { - property var textObj - - MenuItem { - text: qsTr("C&ut") - shortcut: StandardKey.Cut - enabled: textObj.selectedText - onTriggered: textObj.cut() - } - MenuItem { - text: qsTr("&Copy") - shortcut: StandardKey.Copy - enabled: textObj.selectedText - onTriggered: textObj.copy() - } - MenuItem { - text: qsTr("&Paste") - shortcut: StandardKey.Paste - enabled: textObj.canPaste - onTriggered: textObj.paste() - } - - MenuItem { - text: qsTr("&SelectAll") - shortcut: StandardKey.SelectAll - enabled: textObj.length > 0 - onTriggered: textObj.selectAll() - } -} diff --git a/client/ui/qml/Controls/FadeBehavior.qml b/client/ui/qml/Controls/FadeBehavior.qml deleted file mode 100644 index e523061f..00000000 --- a/client/ui/qml/Controls/FadeBehavior.qml +++ /dev/null @@ -1,35 +0,0 @@ -import QtQuick -import QtQml - -Behavior { - id: root - - property QtObject fadeTarget: targetProperty.object - property string fadeProperty: "scale" - property int fadeDuration: 150 - property string easingType: "Quad" - - property alias outAnimation: outAnimation - property alias inAnimation: inAnimation - - SequentialAnimation { - NumberAnimation { - id: outAnimation - target: root.fadeTarget - property: root.fadeProperty - duration: root.fadeDuration - to: 0 - easing.type: Easing["In"+root.easingType] - } - PropertyAction { } - NumberAnimation { - id: inAnimation - target: root.fadeTarget - property: root.fadeProperty - duration: root.fadeDuration - to: target[property] - easing.type: Easing["Out"+root.easingType] - } - } - -} diff --git a/client/ui/qml/Controls/FlickableType.qml b/client/ui/qml/Controls/FlickableType.qml deleted file mode 100644 index 79bfabfd..00000000 --- a/client/ui/qml/Controls/FlickableType.qml +++ /dev/null @@ -1,26 +0,0 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import "../Config" - -Flickable { - id: fl - - clip: true - width: parent.width - - anchors.topMargin: GC.defaultMargin - anchors.bottom: parent.bottom - anchors.bottomMargin: GC.defaultMargin - anchors.left: root.left - anchors.leftMargin: GC.defaultMargin - anchors.right: root.right - anchors.rightMargin: 1 - - Keys.onUpPressed: scrollBar.decrease() - Keys.onDownPressed: scrollBar.increase() - - ScrollBar.vertical: ScrollBar { - id: scrollBar - policy: fl.height >= fl.contentHeight ? ScrollBar.AlwaysOff : ScrollBar.AlwaysOn - } -} diff --git a/client/ui/qml/Controls/ImageButtonType.qml b/client/ui/qml/Controls/ImageButtonType.qml deleted file mode 100644 index 74b90c6e..00000000 --- a/client/ui/qml/Controls/ImageButtonType.qml +++ /dev/null @@ -1,17 +0,0 @@ -import QtQuick -import QtQuick.Controls - -BasicButtonType { - id: root - property alias iconMargin: img.anchors.margins - property alias img: img - property int imgMargin: 4 - property int imgMarginHover: 3 - background: Item {} - contentItem: Image { - id: img - source: root.icon.source - anchors.fill: root - anchors.margins: root.containsMouse ? imgMarginHover : imgMargin - } -} diff --git a/client/ui/qml/Controls/LabelType.qml b/client/ui/qml/Controls/LabelType.qml deleted file mode 100644 index 9cce61b1..00000000 --- a/client/ui/qml/Controls/LabelType.qml +++ /dev/null @@ -1,17 +0,0 @@ -import QtQuick -import "../Config" - -Text { - id: root - width: parent.width - 2 * GC.defaultMargin - anchors.topMargin: 10 - - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#181922" - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - wrapMode: Text.Wrap -} - diff --git a/client/ui/qml/Controls/Logo.qml b/client/ui/qml/Controls/Logo.qml deleted file mode 100644 index 74d82872..00000000 --- a/client/ui/qml/Controls/Logo.qml +++ /dev/null @@ -1,8 +0,0 @@ -import QtQuick -import QtQuick.Controls - -Image { - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottomMargin: 30 - source: "qrc:/images/AmneziaVPN.png" -} diff --git a/client/ui/qml/Controls/PopupWarning.qml b/client/ui/qml/Controls/PopupWarning.qml deleted file mode 100644 index 57c332eb..00000000 --- a/client/ui/qml/Controls/PopupWarning.qml +++ /dev/null @@ -1,34 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -Popup { - id: root - - property string popupWarningText - - anchors.centerIn: Overlay.overlay - modal: true - closePolicy: Popup.NoAutoClose - width: parent.width - 20 - - ColumnLayout { - width: parent.width - Text { - horizontalAlignment: Text.AlignHCenter - Layout.fillWidth: true - wrapMode: Text.WordWrap - font.pixelSize: 16 - text: root.popupWarningText - } - - BlueButtonType { - Layout.preferredWidth: parent.width / 2 - Layout.fillWidth: true - text: "Continue" - onClicked: { - root.close() - } - } - } -} diff --git a/client/ui/qml/Controls/PopupWithQuestion.qml b/client/ui/qml/Controls/PopupWithQuestion.qml deleted file mode 100644 index b55edf90..00000000 --- a/client/ui/qml/Controls/PopupWithQuestion.qml +++ /dev/null @@ -1,62 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -Popup { - id: root - - property string questionText - property string yesText: "yes" - property string noText: "no" - property var yesFunc - property var noFunc - - anchors.centerIn: Overlay.overlay - modal: true - closePolicy: Popup.CloseOnEscape - - width: parent.width - 20 - focus: true - - onAboutToHide: { - parent.forceActiveFocus(true) - } - - ColumnLayout { - width: parent.width - - Text { - horizontalAlignment: Text.AlignHCenter - Layout.fillWidth: true - wrapMode: Text.WordWrap - font.pixelSize: 16 - text: questionText - } - - RowLayout { - Layout.fillWidth: true - BlueButtonType { - id: yesButton - Layout.fillWidth: true - text: yesText - onClicked: { - root.enabled = false - if (yesFunc && typeof yesFunc === "function") { - yesFunc() - } - root.enabled = true - } - } - BlueButtonType { - id: noButton - Layout.fillWidth: true - text: noText - onClicked: { - if (noFunc && typeof noFunc === "function") { - noFunc() - } - } - } - } - } -} diff --git a/client/ui/qml/Controls/PopupWithTextField.qml b/client/ui/qml/Controls/PopupWithTextField.qml deleted file mode 100644 index acdf1247..00000000 --- a/client/ui/qml/Controls/PopupWithTextField.qml +++ /dev/null @@ -1,62 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -Popup { - id: root - - property alias text: textField.text - property alias placeholderText: textField.placeholderText - property string yesText: "yes" - property string noText: "no" - property var yesFunc - property var noFunc - - signal editingFinished() - - anchors.centerIn: Overlay.overlay - modal: true - closePolicy: Popup.NoAutoClose - - width: parent.width - 20 - - ColumnLayout { - width: parent.width - - TextField { - id: textField - horizontalAlignment: Text.AlignHCenter - Layout.fillWidth: true - font.pixelSize: 16 - echoMode: TextInput.Password - } - - RowLayout { - Layout.fillWidth: true - BlueButtonType { - id: yesButton - Layout.preferredWidth: parent.width / 2 - Layout.fillWidth: true - text: yesText - onClicked: { - root.enabled = false - if (yesFunc && typeof yesFunc === "function") { - yesFunc() - } - root.enabled = true - } - } - BlueButtonType { - id: noButton - Layout.preferredWidth: parent.width / 2 - Layout.fillWidth: true - text: noText - onClicked: { - if (noFunc && typeof noFunc === "function") { - noFunc() - } - } - } - } - } -} diff --git a/client/ui/qml/Controls/RadioButtonType.qml b/client/ui/qml/Controls/RadioButtonType.qml deleted file mode 100644 index cda28ea5..00000000 --- a/client/ui/qml/Controls/RadioButtonType.qml +++ /dev/null @@ -1,36 +0,0 @@ -import QtQuick -import QtQuick.Controls - -RadioButton { - id: root - - indicator: Rectangle { - implicitWidth: 13 - implicitHeight: 13 - x: root.leftPadding - y: parent.height / 2 - height / 2 - radius: 13 - border.color: root.down ? "#777777" : "#777777" - - Rectangle { - width: 7 - height: 7 - x: 3 - y: 3 - radius: 4 - color: root.down ? "#15CDCB" : "#15CDCB" - visible: root.checked - } - } - - contentItem: Text { - text: root.text - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: enabled ? "#181922" : "#686972" - verticalAlignment: Text.AlignVCenter - leftPadding: root.indicator.width + root.spacing - } - height: 10 -} diff --git a/client/ui/qml/Controls/RichLabelType.qml b/client/ui/qml/Controls/RichLabelType.qml deleted file mode 100644 index f354f974..00000000 --- a/client/ui/qml/Controls/RichLabelType.qml +++ /dev/null @@ -1,17 +0,0 @@ -import QtQuick - -LabelType { - id: label_connection_code - width: parent.width - 60 - x: 30 - font.pixelSize: 14 - textFormat: Text.RichText - onLinkActivated: Qt.openUrlExternally(link) - - MouseArea { - anchors.fill: parent - cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor - acceptedButtons: Qt.NoButton - } -} - diff --git a/client/ui/qml/Controls/SettingButtonType.qml b/client/ui/qml/Controls/SettingButtonType.qml deleted file mode 100644 index 6166793d..00000000 --- a/client/ui/qml/Controls/SettingButtonType.qml +++ /dev/null @@ -1,33 +0,0 @@ -import QtQuick -import QtQuick.Controls - -BasicButtonType { - id: root - property alias textItem: textItem - height: 30 - - background: Item {} - contentItem: Item { - SvgImageType { - anchors.left: parent.left - anchors.verticalCenter: parent.verticalCenter - svg.source: root.icon.source - enabled: root.enabled - color: "#100A44" - width: 25 - height: 25 - } - Text { - id: textItem - anchors.fill: parent - leftPadding: 30 - text: root.text - color: root.enabled ? "#100A44": "#AAAAAA" - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 20 - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - } - } -} diff --git a/client/ui/qml/Controls/ShareConnectionButtonCopyType.qml b/client/ui/qml/Controls/ShareConnectionButtonCopyType.qml deleted file mode 100644 index 31b3591e..00000000 --- a/client/ui/qml/Controls/ShareConnectionButtonCopyType.qml +++ /dev/null @@ -1,26 +0,0 @@ -import QtQuick -import QtQuick.Controls - -ShareConnectionButtonType { - property string start_text: qsTr("Copy") - property string end_text: qsTr("Copied") - - property string copyText - - enabled: copyText.length > 0 - visible: copyText.length > 0 - - Timer { - id: timer - interval: 1000; running: false; repeat: false - onTriggered: text = start_text - } - - text: start_text - - onClicked: { - text = end_text - timer.running = true - UiLogic.copyToClipboard(copyText) - } -} diff --git a/client/ui/qml/Controls/ShareConnectionButtonType.qml b/client/ui/qml/Controls/ShareConnectionButtonType.qml deleted file mode 100644 index 77ebbac0..00000000 --- a/client/ui/qml/Controls/ShareConnectionButtonType.qml +++ /dev/null @@ -1,27 +0,0 @@ -import QtQuick -import QtQuick.Controls - - -BasicButtonType { - id: root - height: 40 - background: Rectangle { - anchors.fill: parent - radius: 4 - color: root.enabled - ? (root.containsMouse ? "#282932" : "#181922") - : "#484952" - } - font.pixelSize: 16 - contentItem: Text { - anchors.fill: parent - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: root.font.pixelSize - color: "#D4D4D4" - text: root.text - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - antialiasing: true -} diff --git a/client/ui/qml/Controls/ShareConnectionContent.qml b/client/ui/qml/Controls/ShareConnectionContent.qml deleted file mode 100644 index 99427aef..00000000 --- a/client/ui/qml/Controls/ShareConnectionContent.qml +++ /dev/null @@ -1,65 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Shapes 1.4 - -Item { - id: root - property bool active: false - property string text: "" - height: active ? contentLoader.item.height + 40 + 5 * 2 : 40 - signal clicked() - - Rectangle { - x: 0 - y: 0 - width: parent.width - height: 40 - color: "transparent" - clip: true - radius: 2 - gradient: LinearGradient { - x1: 0 ; y1: 0 - x2: 0 ; y2: height - stops: [ - GradientStop { position: 0.0; color: "#E1E1E1" }, - GradientStop { position: 0.4; color: "#DDDDDD" }, - GradientStop { position: 0.5; color: "#D8D8D8" }, - GradientStop { position: 1.0; color: "#D3D3D3" } - ] - } - Image { - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.leftMargin: 10 - source: "qrc:/images/share.png" - } - Rectangle { - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom - height: 2 - color: "#148CD2" - visible: ms.containsMouse ? true : false - } - Text { - x: 40 - anchors.verticalCenter: parent.verticalCenter - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 18 - color: "#100A44" - font.bold: true - text: root.text - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - wrapMode: Text.Wrap - } - MouseArea { - id: ms - anchors.fill: parent - hoverEnabled: true - onClicked: root.clicked() - } - } -} - diff --git a/client/ui/qml/Controls/SvgButtonType.qml b/client/ui/qml/Controls/SvgButtonType.qml deleted file mode 100644 index e6f78c87..00000000 --- a/client/ui/qml/Controls/SvgButtonType.qml +++ /dev/null @@ -1,16 +0,0 @@ -import QtQuick -import QtQuick.Controls -import "." - -BasicButtonType { - id: root - icon.color: "#181922" - - background: Item {} - contentItem: SvgImageType { - svg.source: icon.source - color: icon.color - anchors.fill: parent - anchors.margins: parent.containsMouse ? 0 : 1 - } -} diff --git a/client/ui/qml/Controls/SvgImageType.qml b/client/ui/qml/Controls/SvgImageType.qml deleted file mode 100644 index aee928ba..00000000 --- a/client/ui/qml/Controls/SvgImageType.qml +++ /dev/null @@ -1,23 +0,0 @@ -import QtQuick -import QtQuick.Controls -import Qt5Compat.GraphicalEffects - -Item { - id: root - property color color: "#181922" - property alias svg: image - Image { - anchors.fill: parent - id: image - sourceSize: Qt.size(root.width, root.height) - - antialiasing: true - visible: false - } - - ColorOverlay { - anchors.fill: image - source: image - color: root.enabled ? root.color : "grey" - } -} diff --git a/client/ui/qml/Controls/TextAreaType.qml b/client/ui/qml/Controls/TextAreaType.qml deleted file mode 100644 index 2f6e0843..00000000 --- a/client/ui/qml/Controls/TextAreaType.qml +++ /dev/null @@ -1,63 +0,0 @@ -import QtQuick -import QtQuick.Controls -import Qt.labs.platform - -import "../Config" - -Flickable -{ - property alias textArea: root - id: flickable - flickableDirection: Flickable.VerticalFlick - clip: true - TextArea.flickable: - - TextArea { - id: root - property bool error: false - - height: 40 - anchors.topMargin: 5 - selectByMouse: false - - selectionColor: "darkgray" - font.pixelSize: 16 - color: "#333333" - background: Rectangle { - implicitWidth: 200 - implicitHeight: 40 - border.width: 1 - color: { - if (root.error) { - return Qt.rgba(213, 40, 60, 255) - } - return root.enabled ? "#F4F4F4" : Qt.rgba(127, 127, 127, 255) - } - border.color: { - if (!root.enabled) { - return Qt.rgba(127, 127, 127, 255) - } - if (root.error) { - return Qt.rgba(213, 40, 60, 255) - } - if (root.focus) { - return "#A7A7A7" - } - return "#A7A7A7" - } - } - -// MouseArea { -// anchors.fill: root -// enabled: GC.isDesktop() -// acceptedButtons: Qt.RightButton -// onClicked: contextMenu.open() -// } - -// ContextMenu { -// id: contextMenu -// textObj: root -// } - } - -} diff --git a/client/ui/qml/Controls/TextFieldType.qml b/client/ui/qml/Controls/TextFieldType.qml deleted file mode 100644 index 5d7b2a65..00000000 --- a/client/ui/qml/Controls/TextFieldType.qml +++ /dev/null @@ -1,52 +0,0 @@ -import QtQuick -import QtQuick.Controls -import Qt.labs.platform -import "../Config" - -TextField { - id: root - property bool error: false - - width: parent.width - 2 * GC.defaultMargin - height: 40 - anchors.topMargin: 5 - selectByMouse: true - selectionColor: "darkgray" - font.pixelSize: 16 - color: "#333333" - - background: Rectangle { - implicitWidth: 200 - implicitHeight: 40 - border.width: 1 - color: { - if (root.error) { - return Qt.rgba(213, 40, 60, 255) - } - return root.enabled ? "#F4F4F4" : Qt.rgba(127, 127, 127, 255) - } - border.color: { - if (!root.enabled) { - return Qt.rgba(127, 127, 127, 255) - } - if (root.error) { - return Qt.rgba(213, 40, 60, 255) - } - if (root.focus) { - return "#A7A7A7" - } - return "#A7A7A7" - } - } - - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.RightButton - onClicked: contextMenu.open() - } - - ContextMenu { - id: contextMenu - textObj: root - } -} diff --git a/client/ui/qml/Controls/UrlButtonType.qml b/client/ui/qml/Controls/UrlButtonType.qml deleted file mode 100644 index d77763dc..00000000 --- a/client/ui/qml/Controls/UrlButtonType.qml +++ /dev/null @@ -1,25 +0,0 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 - -BasicButtonType { - property alias label: lbl - id: root - antialiasing: true - height: 21 - background: Item {} - - contentItem: Text { - id: lbl - anchors.fill: parent - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 18 - font.underline: true - - text: root.text - color: "#3045ee" - - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } -} diff --git a/client/ui/qml/Controls/VisibleBehavior.qml b/client/ui/qml/Controls/VisibleBehavior.qml deleted file mode 100644 index 9aeb4e85..00000000 --- a/client/ui/qml/Controls/VisibleBehavior.qml +++ /dev/null @@ -1,6 +0,0 @@ -FadeBehavior { - fadeProperty: "opacity" - fadeDuration: 200 - outAnimation.duration: targetValue ? 0 : fadeDuration - inAnimation.duration: targetValue ? fadeDuration : 0 -} diff --git a/client/ui/qml/Pages/ClientInfo/PageClientInfoBase.qml b/client/ui/qml/Pages/ClientInfo/PageClientInfoBase.qml deleted file mode 100644 index 7fde5bab..00000000 --- a/client/ui/qml/Pages/ClientInfo/PageClientInfoBase.qml +++ /dev/null @@ -1,15 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageBase { - id: root - property var protocol: ProtocolEnum.Any - page: PageEnum.ClientInfo - logic: ClientInfoLogic -} diff --git a/client/ui/qml/Pages/ClientInfo/PageClientInfoOpenVPN.qml b/client/ui/qml/Pages/ClientInfo/PageClientInfoOpenVPN.qml deleted file mode 100644 index da5ba53c..00000000 --- a/client/ui/qml/Pages/ClientInfo/PageClientInfoOpenVPN.qml +++ /dev/null @@ -1,115 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageClientInfoBase { - id: root - protocol: ProtocolEnum.OpenVpn - - BackButton { - id: back - enabled: !ClientInfoLogic.busyIndicatorIsRunning - } - - Caption { - id: caption - text: qsTr("Client Info") - } - - BusyIndicator { - z: 99 - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - visible: ClientInfoLogic.busyIndicatorIsRunning - running: ClientInfoLogic.busyIndicatorIsRunning - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - visible: ClientInfoLogic.pageContentVisible - - ColumnLayout { - id: content - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: GC.defaultMargin - - LabelType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.fillWidth: true - font.pixelSize: 20 - horizontalAlignment: Text.AlignHCenter - text: ClientInfoLogic.labelCurrentVpnProtocolText - } - - LabelType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - height: 21 - text: qsTr("Client name") - } - - TextFieldType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.fillWidth: true - Layout.preferredHeight: 31 - text: ClientInfoLogic.lineEditNameAliasText - onEditingFinished: { - if (text !== ClientInfoLogic.lineEditNameAliasText) { - ClientInfoLogic.lineEditNameAliasText = text - ClientInfoLogic.onLineEditNameAliasEditingFinished() - } - } - } - - LabelType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.topMargin: 20 - height: 21 - text: qsTr("Certificate id") - } - - LabelType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.fillWidth: true - horizontalAlignment: Text.AlignHCenter - text: ClientInfoLogic.labelOpenVpnCertId - } - - LabelType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.topMargin: 20 - height: 21 - text: qsTr("Certificate") - } - - TextAreaType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.preferredHeight: 200 - Layout.fillWidth: true - - textArea.readOnly: true - textArea.wrapMode: TextEdit.WrapAnywhere - textArea.verticalAlignment: Text.AlignTop - textArea.text: ClientInfoLogic.textAreaOpenVpnCertData - } - - BlueButtonType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.fillWidth: true - Layout.preferredHeight: 41 - text: qsTr("Revoke Certificate") - onClicked: { - ClientInfoLogic.onRevokeOpenVpnCertificateClicked() - UiLogic.closePage() - } - } - } - } -} diff --git a/client/ui/qml/Pages/ClientInfo/PageClientInfoWireGuard.qml b/client/ui/qml/Pages/ClientInfo/PageClientInfoWireGuard.qml deleted file mode 100644 index befaf7f8..00000000 --- a/client/ui/qml/Pages/ClientInfo/PageClientInfoWireGuard.qml +++ /dev/null @@ -1,100 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageClientInfoBase { - id: root - protocol: ProtocolEnum.WireGuard - - BackButton { - id: back - enabled: !ClientInfoLogic.busyIndicatorIsRunning - } - - Caption { - id: caption - text: qsTr("Client Info") - } - - BusyIndicator { - z: 99 - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - visible: ClientInfoLogic.busyIndicatorIsRunning - running: ClientInfoLogic.busyIndicatorIsRunning - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - - ColumnLayout { - id: content - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: GC.defaultMargin - - LabelType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.fillWidth: true - font.pixelSize: 20 - horizontalAlignment: Text.AlignHCenter - text: ClientInfoLogic.labelCurrentVpnProtocolText - } - - LabelType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - height: 21 - text: qsTr("Client name") - } - - TextFieldType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.fillWidth: true - Layout.preferredHeight: 31 - text: ClientInfoLogic.lineEditNameAliasText - onEditingFinished: { - if (text !== ClientInfoLogic.lineEditNameAliasText) { - ClientInfoLogic.lineEditNameAliasText = text - ClientInfoLogic.onLineEditNameAliasEditingFinished() - } - } - } - - LabelType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.topMargin: 20 - height: 21 - text: qsTr("Public Key") - } - - TextAreaType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.preferredHeight: 200 - Layout.fillWidth: true - - textArea.readOnly: true - textArea.wrapMode: TextEdit.WrapAnywhere - textArea.verticalAlignment: Text.AlignTop - textArea.text: ClientInfoLogic.textAreaWireGuardKeyData - } - - BlueButtonType { - enabled: !ClientInfoLogic.busyIndicatorIsRunning - Layout.fillWidth: true - Layout.preferredHeight: 41 - text: qsTr("Revoke Key") - onClicked: { - ClientInfoLogic.onRevokeWireGuardKeyClicked() - UiLogic.closePage() - } - } - } - } -} diff --git a/client/ui/qml/Pages/InstallSettings/InstallSettingsBase.qml b/client/ui/qml/Pages/InstallSettings/InstallSettingsBase.qml deleted file mode 100644 index 8e04c605..00000000 --- a/client/ui/qml/Pages/InstallSettings/InstallSettingsBase.qml +++ /dev/null @@ -1,75 +0,0 @@ -import QtQuick -import QtQuick.Controls -import "./" -import "../../Controls" -import "../../Config" - -Rectangle { - signal containerChecked(bool checked) - property bool initiallyChecked: false - property string containerDescription - default property alias itemSettings: container.data - - x: 5 - y: 5 - width: parent.width - 20 - anchors.horizontalCenter: parent.horizontalCenter - - height: frame_settings.visible ? 140 : 72 - border.width: 1 - border.color: "lightgray" - radius: 2 - Rectangle { - id: frame_settings - height: 77 - width: parent.width - border.width: 1 - border.color: "lightgray" - anchors.bottom: parent.bottom - anchors.bottomMargin: 5 - anchors.horizontalCenter: parent.horizontalCenter - visible: false - radius: 2 - Grid { - id: container - anchors.fill: parent - columns: 2 - horizontalItemAlignment: Grid.AlignHCenter - verticalItemAlignment: Grid.AlignVCenter - topPadding: 5 - leftPadding: 10 - spacing: 5 - } - } - Row { - anchors.top: parent.top - anchors.topMargin: 5 - leftPadding: 15 - rightPadding: 5 - height: 55 - width: parent.width - CheckBoxType { - text: containerDescription - height: parent.height - width: parent.width - 50 - checked: initiallyChecked - onCheckedChanged: containerChecked(checked) - } - ImageButtonType { - width: 35 - height: 35 - anchors.verticalCenter: parent.verticalCenter - icon.source: "qrc:/images/settings.png" - checkable: true - checked: initiallyChecked - onCheckedChanged: { - //NewServerProtocolsLogic.pushButtonSettingsCloakChecked = checked - if (checked) { - frame_settings.visible = true - } else { - frame_settings.visible = false - } - } - } - } -} diff --git a/client/ui/qml/Pages/InstallSettings/SelectContainer.qml b/client/ui/qml/Pages/InstallSettings/SelectContainer.qml deleted file mode 100644 index 59e8f464..00000000 --- a/client/ui/qml/Pages/InstallSettings/SelectContainer.qml +++ /dev/null @@ -1,200 +0,0 @@ -import QtQuick -import QtQuick.Controls -import SortFilterProxyModel 0.2 -import ProtocolEnum 1.0 -import "./" -import "../../Controls" -import "../../Config" - -Drawer { - id: root - signal containerSelected(int c_index) - property int selectedIndex: -1 - - y: 0 - x: 0 - edge: Qt.RightEdge - width: parent.width * 0.85 - height: parent.height - - modal: true - interactive: activeFocus - - SortFilterProxyModel { - id: proxyModel - sourceModel: UiLogic.containersModel - filters: [ - ValueFilter { - roleName: "is_installed_role" - value: false }, - ValueFilter { - roleName: "service_type_role" - value: ProtocolEnum.Vpn } - ] - - } - - SortFilterProxyModel { - id: proxyModel_other - sourceModel: UiLogic.containersModel - filters: [ - ValueFilter { - roleName: "is_installed_role" - value: false }, - ValueFilter { - roleName: "service_type_role" - value: ProtocolEnum.Other } - ] - - } - - FlickableType { - anchors.fill: parent - contentHeight: col.height - - Column { - id: col - anchors { - left: parent.left; - right: parent.right; - } - topPadding: 20 - spacing: 10 - - Caption { - id: cap1 - text: qsTr("VPN containers") - font.pixelSize: 20 - - } - - ListView { - id: tb - x: 10 - currentIndex: -1 - width: parent.width - 20 - height: contentItem.height - - spacing: 0 - clip: true - interactive: false - model: proxyModel - - delegate: Item { - implicitWidth: 170 * 2 - implicitHeight: 30 - Item { - width: parent.width - height: 30 - anchors.left: parent.left - id: c1 - Rectangle { - anchors.top: parent.top - width: parent.width - height: 1 - color: "lightgray" - visible: index !== tb.currentIndex - } - Rectangle { - anchors.fill: parent - color: "#63B4FB" - visible: index === tb.currentIndex - - } - Text { - id: text_name - text: name_role - font.pixelSize: 16 - anchors.fill: parent - leftPadding: 10 - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - } - } - - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: { - tb.currentIndex = index - tb_other.currentIndex = -1 - containerSelected(proxyModel.mapToSource(index)) - selectedIndex = proxyModel.mapToSource(index) - root.close() - } - } - } - } - - - Caption { - id: cap2 - font.pixelSize: 20 - text: qsTr("Other containers") - } - - ListView { - id: tb_other - x: 10 - currentIndex: -1 - width: parent.width - 20 - height: contentItem.height - - spacing: 0 - clip: true - interactive: false - model: proxyModel_other - - delegate: Item { - implicitWidth: 170 * 2 - implicitHeight: 30 - Item { - width: parent.width - height: 30 - anchors.left: parent.left - id: c1_other - Rectangle { - anchors.top: parent.top - width: parent.width - height: 1 - color: "lightgray" - visible: index !== tb_other.currentIndex - } - Rectangle { - anchors.fill: parent - color: "#63B4FB" - visible: index === tb_other.currentIndex - - } - Text { - id: text_name_other - text: name_role - font.pixelSize: 16 - anchors.fill: parent - leftPadding: 10 - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - } - } - - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: { - tb_other.currentIndex = index - tb.currentIndex = -1 - containerSelected(proxyModel_other.mapToSource(index)) - selectedIndex = proxyModel_other.mapToSource(index) - root.close() - } - } - } - } - - - } - - - } - -} diff --git a/client/ui/qml/Pages/PageAbout.qml b/client/ui/qml/Pages/PageAbout.qml deleted file mode 100644 index ab25c23e..00000000 --- a/client/ui/qml/Pages/PageAbout.qml +++ /dev/null @@ -1,90 +0,0 @@ -import QtQuick -import QtQuick.Controls -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.About - - BackButton { - id: back_from_start - } - - Caption { - id: caption - font.pixelSize: 22 - text: qsTr("About Amnezia") - } - - RichLabelType { - id: label_about - anchors.top: caption.bottom - - text: qsTr("AmneziaVPN is opensource software, it's free forever. Our goal is to make the best VPN client in the world. -

-") - } - - Caption { - id: caption2 - anchors.topMargin: 20 - font.pixelSize: 22 - text: qsTr("Support") - anchors.top: label_about.bottom - } - - RichLabelType { - id: label_support - anchors.top: caption2.bottom - - text: qsTr("Have questions? You can get support by: -") - } - - Caption { - id: caption3 - anchors.topMargin: 20 - font.pixelSize: 22 - text: qsTr("Donate") - width: undefined - anchors.top: label_support.bottom - } - - LabelType { - anchors.bottom: caption3.bottom - anchors.left: caption3.right - anchors.leftMargin: 5 - font.pixelSize: 24 - text: "♥" - color: "red" - } - - RichLabelType { - id: label_donate - anchors.top: caption3.bottom - - text: qsTr("Please support Amnezia project by donation, we really need it now more than ever. - -") - } - - Logo { - id: logo - anchors.bottom: parent.bottom - } -} diff --git a/client/ui/qml/Pages/PageAdvancedServerSettings.qml b/client/ui/qml/Pages/PageAdvancedServerSettings.qml deleted file mode 100644 index 9f71b2ba..00000000 --- a/client/ui/qml/Pages/PageAdvancedServerSettings.qml +++ /dev/null @@ -1,118 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.AdvancedServerSettings - logic: AdvancedServerSettingsLogic - - enabled: AdvancedServerSettingsLogic.pageEnabled - - BackButton { - id: back - } - - Caption { - id: caption - text: qsTr("Advanced server settings") - anchors.horizontalCenter: parent.horizontalCenter - } - - BusyIndicator { - z: 99 - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - visible: !AdvancedServerSettingsLogic.pageEnabled - running: !AdvancedServerSettingsLogic.pageEnabled - } - - FlickableType { - id: fl - anchors.top: caption.bottom - anchors.bottom: logo.top - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - LabelType { - Layout.fillWidth: true - font.pixelSize: 20 - horizontalAlignment: Text.AlignHCenter - text: AdvancedServerSettingsLogic.labelCurrentVpnProtocolText - } - - TextFieldType { - Layout.fillWidth: true - font.pixelSize: 20 - horizontalAlignment: Text.AlignHCenter - text: AdvancedServerSettingsLogic.labelServerText - readOnly: true - background: Item {} - } - - LabelType { - Layout.fillWidth: true - horizontalAlignment: Text.AlignHCenter - text: AdvancedServerSettingsLogic.labelWaitInfoText - visible: AdvancedServerSettingsLogic.labelWaitInfoVisible - } - - BlueButtonType { - Layout.fillWidth: true - Layout.topMargin: 10 - text: "Scan the server for installed containers" - visible: AdvancedServerSettingsLogic.pushButtonClearVisible - onClicked: { - AdvancedServerSettingsLogic.onPushButtonScanServerClicked() - } - } - - BlueButtonType { - Layout.fillWidth: true - Layout.topMargin: 10 - text: AdvancedServerSettingsLogic.pushButtonClearText - visible: AdvancedServerSettingsLogic.pushButtonClearVisible - onClicked: { - popupClearServer.open() - } - } - - BlueButtonType { - Layout.topMargin: 10 - Layout.fillWidth: true - text: qsTr("Clients Management") - onClicked: { - UiLogic.goToPage(PageEnum.ClientManagement) - } - } - - PopupWithQuestion { - id: popupClearServer - questionText: "Attention! All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted. Continue?" - yesFunc: function() { - close() - AdvancedServerSettingsLogic.onPushButtonClearServerClicked() - } - noFunc: function() { - close() - } - } - } - } - - Logo { - id : logo - anchors.bottom: parent.bottom - } -} diff --git a/client/ui/qml/Pages/PageAppSetting.qml b/client/ui/qml/Pages/PageAppSetting.qml deleted file mode 100644 index 2bf0e306..00000000 --- a/client/ui/qml/Pages/PageAppSetting.qml +++ /dev/null @@ -1,152 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.AppSettings - logic: AppSettingsLogic - - BackButton { - id: back - } - Caption { - id: caption - text: qsTr("Application Settings") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - CheckBoxType { - visible: !GC.isMobile() - Layout.fillWidth: true - text: qsTr("Auto connect") - checked: AppSettingsLogic.checkBoxAutoConnectChecked - onCheckedChanged: { - AppSettingsLogic.checkBoxAutoConnectChecked = checked - AppSettingsLogic.onCheckBoxAutoconnectToggled(checked) - } - } - CheckBoxType { - visible: !GC.isMobile() - Layout.fillWidth: true - text: qsTr("Auto start") - checked: AppSettingsLogic.checkBoxAutostartChecked - onCheckedChanged: { - AppSettingsLogic.checkBoxAutostartChecked = checked - AppSettingsLogic.onCheckBoxAutostartToggled(checked) - } - } - CheckBoxType { - visible: !GC.isMobile() - Layout.fillWidth: true - text: qsTr("Start minimized") - checked: AppSettingsLogic.checkBoxStartMinimizedChecked - onCheckedChanged: { - AppSettingsLogic.checkBoxStartMinimizedChecked = checked - AppSettingsLogic.onCheckBoxStartMinimizedToggled(checked) - } - } - LabelType { - Layout.fillWidth: true - Layout.topMargin: 15 - text: AppSettingsLogic.labelVersionText - } - BlueButtonType { - visible: !GC.isMobile() - Layout.fillWidth: true - text: qsTr("Check for updates") - onClicked: { - Qt.openUrlExternally("https://github.com/amnezia-vpn/desktop-client/releases/latest") - } - } - - CheckBoxType { - Layout.fillWidth: true - Layout.topMargin: 15 - text: qsTr("Keep logs") - checked: AppSettingsLogic.checkBoxSaveLogsChecked - onCheckedChanged: { - AppSettingsLogic.checkBoxSaveLogsChecked = checked - AppSettingsLogic.onCheckBoxSaveLogsCheckedToggled(checked) - } - } - BlueButtonType { - Layout.fillWidth: true - text: qsTr("Open logs folder") - onClicked: { - AppSettingsLogic.onPushButtonOpenLogsClicked() - } - } - - BlueButtonType { - Layout.fillWidth: true - Layout.topMargin: 10 - text: qsTr("Export logs") - onClicked: { - AppSettingsLogic.onPushButtonExportLogsClicked() - } - } - - BlueButtonType { - Layout.fillWidth: true - Layout.topMargin: 10 - - property string start_text: qsTr("Clear logs") - property string end_text: qsTr("Cleared") - text: start_text - - Timer { - id: timer - interval: 1000; running: false; repeat: false - onTriggered: parent.text = parent.start_text - } - onClicked: { - text = end_text - timer.running = true - AppSettingsLogic.onPushButtonClearLogsClicked() - } - } - - LabelType { - Layout.fillWidth: true - Layout.topMargin: 30 - text: qsTr("Backup and restore configuration") - } - - BlueButtonType { - Layout.fillWidth: true - Layout.topMargin: 10 - Layout.preferredHeight: 41 - text: qsTr("Backup app config") - onClicked: { - AppSettingsLogic.onPushButtonBackupAppConfigClicked() - } - } - BlueButtonType { - Layout.fillWidth: true - Layout.topMargin: 10 - Layout.preferredHeight: 41 - text: qsTr("Restore app config") - onClicked: { - AppSettingsLogic.onPushButtonRestoreAppConfigClicked() - } - } - } - } -} diff --git a/client/ui/qml/Pages/PageBase.qml b/client/ui/qml/Pages/PageBase.qml deleted file mode 100644 index c398515b..00000000 --- a/client/ui/qml/Pages/PageBase.qml +++ /dev/null @@ -1,20 +0,0 @@ -import QtQuick -import QtQuick.Controls -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -Item { - id: root - property var page: PageEnum.Start - property var logic: UiLogic - - property bool pageActive: false - - signal activated(bool reset) - signal deactivated() - - onActivated: pageActive = true - onDeactivated: pageActive = false -} diff --git a/client/ui/qml/Pages/PageClientManagement.qml b/client/ui/qml/Pages/PageClientManagement.qml deleted file mode 100644 index 39defe4c..00000000 --- a/client/ui/qml/Pages/PageClientManagement.qml +++ /dev/null @@ -1,119 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import QtQuick.Shapes 1.4 -import SortFilterProxyModel 0.2 -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.ClientManagement - logic: ClientManagementLogic - enabled: !ClientManagementLogic.busyIndicatorIsRunning - - BackButton { - id: back - } - - Caption { - id: caption - text: qsTr("Clients Management") - } - - BusyIndicator { - z: 99 - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - visible: ClientManagementLogic.busyIndicatorIsRunning - running: ClientManagementLogic.busyIndicatorIsRunning - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - - Column { - id: content - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - LabelType { - font.pixelSize: 20 - leftPadding: -20 - anchors.horizontalCenter: parent.horizontalCenter - horizontalAlignment: Text.AlignHCenter - text: ClientManagementLogic.labelCurrentVpnProtocolText - } - - SortFilterProxyModel { - id: proxyClientManagementModel - sourceModel: UiLogic.clientManagementModel - sorters: RoleSorter { roleName: "clientName" } - } - - ListView { - id: lv_clients - width: parent.width - implicitHeight: contentHeight + 20 - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 20 - topMargin: 10 - spacing: 10 - clip: true - model: proxyClientManagementModel - highlightRangeMode: ListView.ApplyRange - highlightMoveVelocity: -1 - delegate: Item { - implicitWidth: lv_clients.width - implicitHeight: 60 - - MouseArea { - id: ms - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: { - ClientManagementLogic.onClientItemClicked(proxyClientManagementModel.mapToSource(index)) - } - } - - Rectangle { - anchors.fill: parent - gradient: ms.containsMouse ? gradient_containsMouse : gradient_notContainsMouse - LinearGradient { - id: gradient_notContainsMouse - x1: 0 ; y1:0 - x2: 0 ; y2: height - stops: [ - GradientStop { position: 0.0; color: "#FAFBFE" }, - GradientStop { position: 1.0; color: "#ECEEFF" } - ] - } - LinearGradient { - id: gradient_containsMouse - x1: 0 ; y1:0 - x2: 0 ; y2: height - stops: [ - GradientStop { position: 0.0; color: "#FAFBFE" }, - GradientStop { position: 1.0; color: "#DCDEDF" } - ] - } - } - - LabelType { - x: 20 - y: 20 - font.pixelSize: 20 - text: clientName - } - } - } - } - } -} diff --git a/client/ui/qml/Pages/PageGeneralSettings.qml b/client/ui/qml/Pages/PageGeneralSettings.qml deleted file mode 100644 index c85aa8a7..00000000 --- a/client/ui/qml/Pages/PageGeneralSettings.qml +++ /dev/null @@ -1,166 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.GeneralSettings - logic: GeneralSettingsLogic - - BackButton { - id: back - z: -1 - } - - FlickableType { - id: fl - anchors.top: back.bottom - anchors.topMargin: 0 - anchors.bottomMargin: 10 - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.topMargin: 10 - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: GC.defaultMargin - - spacing: 15 - - - // ---------- App settings ------------ - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 1 - color: "#DDDDDD" - } - SettingButtonType { - Layout.fillWidth: true - Layout.preferredHeight: 30 - icon.source: "qrc:/images/svg/settings_black_24dp.svg" - text: qsTr("App settings") - onClicked: { - UiLogic.goToPage(PageEnum.AppSettings) - } - } - - // ---------- Network settings ------------ - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 1 - color: "#DDDDDD" - } - SettingButtonType { - Layout.fillWidth: true - Layout.preferredHeight: 30 - icon.source: "qrc:/images/svg/settings_suggest_black_24dp.svg" - text: qsTr("Network settings") - onClicked: { - UiLogic.goToPage(PageEnum.NetworkSettings) - } - } - - // ---------- Server settings ------------ - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 1 - color: "#DDDDDD" - } - SettingButtonType { - Layout.fillWidth: true - Layout.preferredHeight: 30 - icon.source: "qrc:/images/svg/vpn_key_black_24dp.svg" - text: qsTr("Server Settings") - enabled: GeneralSettingsLogic.existsAnyServer - onClicked: { - GeneralSettingsLogic.onPushButtonGeneralSettingsServerSettingsClicked() - } - } - - // ---------- Share connection ------------ - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 1 - color: "#DDDDDD" - } - SettingButtonType { - Layout.fillWidth: true - Layout.preferredHeight: 30 - icon.source: "qrc:/images/svg/share_black_24dp.svg" - text: qsTr("Share connection") - enabled: GeneralSettingsLogic.pushButtonGeneralSettingsShareConnectionEnable && - GeneralSettingsLogic.existsAnyServer - onClicked: { - GeneralSettingsLogic.onPushButtonGeneralSettingsShareConnectionClicked() - } - } - - // ---------- Servers ------------ - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 1 - color: "#DDDDDD" - } - SettingButtonType { - Layout.fillWidth: true - Layout.preferredHeight: 30 - icon.source: "qrc:/images/svg/format_list_bulleted_black_24dp.svg" - text: qsTr("Servers") - enabled: GeneralSettingsLogic.existsAnyServer - onClicked: { - UiLogic.goToPage(PageEnum.ServersList) - } - } - - // ---------- Add server ------------ - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 1 - color: "#DDDDDD" - } - SettingButtonType { - Layout.fillWidth: true - Layout.preferredHeight: 30 - icon.source: "qrc:/images/svg/control_point_black_24dp.svg" - text: qsTr("Add server") - onClicked: { - if(GeneralSettingsLogic.existsAnyServer) - // If there is any server set we will go to Start Page - UiLogic.goToPage(PageEnum.Start) - else - // Else just come back to start page - UiLogic.closePage() - } - } - - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 1 - color: "#DDDDDD" - } - - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: fl.height > (75+1) * 6 ? fl.height - (75+1) * 6 : 0 - } - - SettingButtonType { - Layout.fillWidth: true - Layout.preferredHeight: 30 - Layout.bottomMargin: 20 - icon.source: "qrc:/images/svg/logout_black_24dp.svg" - text: qsTr("Exit") - onClicked: { - Qt.quit() - } - } - } - } -} diff --git a/client/ui/qml/Pages/PageNetworkSetting.qml b/client/ui/qml/Pages/PageNetworkSetting.qml deleted file mode 100644 index e14c04a6..00000000 --- a/client/ui/qml/Pages/PageNetworkSetting.qml +++ /dev/null @@ -1,113 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.NetworkSettings - logic: NetworkSettingsLogic - - BackButton { - id: back - } - Caption { - id: caption - text: qsTr("DNS Servers") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - CheckBoxType { - Layout.preferredWidth: parent.width - text: qsTr("Use AmneziaDNS service (recommended)") - checked: NetworkSettingsLogic.checkBoxUseAmneziaDnsChecked - onCheckedChanged: { - NetworkSettingsLogic.checkBoxUseAmneziaDnsChecked = checked - NetworkSettingsLogic.onCheckBoxUseAmneziaDnsToggled(checked) - UiLogic.onUpdateAllPages() - } - } - - LabelType { - Layout.preferredWidth: parent.width - text: qsTr("Use AmneziaDNS container on your server, when it installed.\n -Your AmneziaDNS server available only when it installed and VPN connected, it has internal IP address 172.29.172.254\n -If AmneziaDNS service is not installed on the same server, or this option is unchecked, the following DNS servers will be used:") - } - - LabelType { - Layout.topMargin: 15 - text: qsTr("Primary DNS server") - } - TextFieldType { - height: 40 - implicitWidth: parent.width - text: NetworkSettingsLogic.lineEditDns1Text - onEditingFinished: { - NetworkSettingsLogic.lineEditDns1Text = text - NetworkSettingsLogic.onLineEditDns1EditFinished(text) - UiLogic.onUpdateAllPages() - } - validator: RegularExpressionValidator { - regularExpression: NetworkSettingsLogic.ipAddressRegex - } - } - - UrlButtonType { - text: qsTr("Reset to default") - label.horizontalAlignment: Text.AlignLeft - label.verticalAlignment: Text.AlignTop - label.font.pixelSize: 14 - icon.source: "qrc:/images/svg/refresh_black_24dp.svg" - onClicked: { - NetworkSettingsLogic.onPushButtonResetDns1Clicked() - UiLogic.onUpdateAllPages() - } - } - - LabelType { - text: qsTr("Secondary DNS server") - } - TextFieldType { - height: 40 - implicitWidth: parent.width - text: NetworkSettingsLogic.lineEditDns2Text - onEditingFinished: { - NetworkSettingsLogic.lineEditDns2Text = text - NetworkSettingsLogic.onLineEditDns2EditFinished(text) - UiLogic.onUpdateAllPages() - } - validator: RegularExpressionValidator { - regularExpression: NetworkSettingsLogic.ipAddressRegex - } - } - - UrlButtonType { - text: qsTr("Reset to default") - label.horizontalAlignment: Text.AlignLeft - label.verticalAlignment: Text.AlignTop - label.font.pixelSize: 14 - icon.source: "qrc:/images/svg/refresh_black_24dp.svg" - onClicked: { - NetworkSettingsLogic.onPushButtonResetDns2Clicked() - UiLogic.onUpdateAllPages() - } - } - } - } -} diff --git a/client/ui/qml/Pages/PageNewServer.qml b/client/ui/qml/Pages/PageNewServer.qml deleted file mode 100644 index 00cb51bc..00000000 --- a/client/ui/qml/Pages/PageNewServer.qml +++ /dev/null @@ -1,61 +0,0 @@ -import QtQuick -import QtQuick.Controls -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.NewServer - - BackButton { - id: back_from_new_server - } - Caption { - id: caption - text: qsTr("Setup your server to use VPN") - } - LabelType { - id: labelWizard - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - text: qsTr("If you want easily configure your server just run Wizard") - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: caption.bottom - anchors.topMargin: 30 - } - BlueButtonType { - id: pushButtonWizard - text: qsTr("Run Setup Wizard") - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: labelWizard.bottom - anchors.topMargin: 10 - onClicked: { - UiLogic.goToPage(PageEnum.Wizard); - } - } - LabelType { - id: labelManual - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - text: qsTr("Press configure manually to choose VPN protocols you want to install") - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: pushButtonWizard.bottom - anchors.topMargin: 40 - } - - BlueButtonType { - text: qsTr("Configure") - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: labelManual.bottom - anchors.topMargin: 10 - onClicked: { - UiLogic.goToPage(PageEnum.NewServerProtocols); - } - } - - Logo { - anchors.bottom: parent.bottom - } -} diff --git a/client/ui/qml/Pages/PageNewServerProtocols.qml b/client/ui/qml/Pages/PageNewServerProtocols.qml deleted file mode 100644 index 0ce2090f..00000000 --- a/client/ui/qml/Pages/PageNewServerProtocols.qml +++ /dev/null @@ -1,154 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ContainerProps 1.0 -import ProtocolProps 1.0 -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" -import "InstallSettings" - -PageBase { - id: root - page: PageEnum.NewServerProtocols - logic: NewServerProtocolsLogic - - onActivated: { - container_selector.selectedIndex = -1 - UiLogic.containersModel.setSelectedServerIndex(-1) - } - - BackButton { - id: back - } - Caption { - id: caption - text: qsTr("Select VPN protocols") - } - - BlueButtonType { - id: pushButtonConfigure - enabled: container_selector.selectedIndex > 0 - anchors.horizontalCenter: parent.horizontalCenter - y: parent.height - 60 - width: parent.width - 40 - height: 40 - text: qsTr("Setup server") - onClicked: { - let cont = container_selector.selectedIndex - let tp = ProtocolProps.transportProtoFromString(cb_port_proto.currentText) - let port = tf_port_num.text - NewServerProtocolsLogic.onPushButtonConfigureClicked(cont, port, tp) - } - } - - BlueButtonType { - id: pb_add_container - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: caption.bottom - anchors.topMargin: 10 - - width: parent.width - 40 - height: 40 - text: qsTr("Select protocol container") - font.pixelSize: 16 - onClicked: container_selector.visible ? container_selector.close() : container_selector.open() - - } - - SelectContainer { - id: container_selector - onAboutToHide: { - pageLoader.focus = true - } - - onContainerSelected: function(c_index){ - var containerProto = ContainerProps.defaultProtocol(c_index) - - tf_port_num.text = ProtocolProps.defaultPort(containerProto) - cb_port_proto.currentIndex = ProtocolProps.defaultTransportProto(containerProto) - - tf_port_num.enabled = ProtocolProps.defaultPortChangeable(containerProto) - cb_port_proto.enabled = ProtocolProps.defaultTransportProtoChangeable(containerProto) - } - } - - Column { - id: c1 - visible: container_selector.selectedIndex > 0 - width: parent.width - anchors.top: pb_add_container.bottom - anchors.topMargin: 10 - - Caption { - font.pixelSize: 22 - text: UiLogic.containerName(container_selector.selectedIndex) - } - - Text { - width: parent.width - anchors.topMargin: 10 - padding: 10 - - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#181922" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.Wrap - - text: UiLogic.containerDesc(container_selector.selectedIndex) - } - } - - - - Rectangle { - id: frame_settings - visible: container_selector.selectedIndex > 0 - width: parent.width - anchors.top: c1.bottom - anchors.topMargin: 10 - - border.width: 1 - border.color: "lightgray" - anchors.bottomMargin: 5 - anchors.horizontalCenter: parent.horizontalCenter - radius: 2 - Grid { - id: grid - visible: container_selector.selectedIndex > 0 - anchors.fill: parent - columns: 2 - horizontalItemAlignment: Grid.AlignHCenter - verticalItemAlignment: Grid.AlignVCenter - topPadding: 5 - leftPadding: 10 - spacing: 5 - - - LabelType { - width: 130 - text: qsTr("Port") - } - TextFieldType { - id: tf_port_num - width: parent.width - 130 - parent.spacing - parent.leftPadding * 2 - } - LabelType { - width: 130 - text: qsTr("Network Protocol") - } - ComboBoxType { - id: cb_port_proto - width: parent.width - 130 - parent.spacing - parent.leftPadding * 2 - model: [ - qsTr("udp"), - qsTr("tcp"), - ] - } - } - } -} diff --git a/client/ui/qml/Pages/PageQrDecoderIos.qml b/client/ui/qml/Pages/PageQrDecoderIos.qml deleted file mode 100644 index 21bdbfe7..00000000 --- a/client/ui/qml/Pages/PageQrDecoderIos.qml +++ /dev/null @@ -1,94 +0,0 @@ -import QtQuick -import QtQuick.Controls -import PageEnum 1.0 -import QRCodeReader 1.0 - -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.QrDecoderIos - logic: QrDecoderLogic - - onDeactivated: { - console.debug("Stopping QR decoder") - loader.sourceComponent = undefined - } - - BackButton { - } - Caption { - id: caption - text: qsTr("Import configuration") - } - - Connections { - target: Qt.platform.os == "ios" ? QrDecoderLogic : null - function onStartDecode() { - console.debug("Starting QR decoder") - loader.sourceComponent = component - } - function onStopDecode() { - console.debug("Stopping QR decoder") - loader.sourceComponent = undefined - } - } - - Loader { - id: loader - - anchors.top: caption.bottom - anchors.bottom: progressColumn.top - anchors.left: parent.left - anchors.right: parent.right - } - - Column{ - height: 40 - id: progressColumn - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: parent.right - ProgressBar { - id: progress - anchors.left: parent.left - anchors.right: parent.right - value: QrDecoderLogic.totalChunksCount === 0? 0 : (QrDecoderLogic.receivedChunksCount/QrDecoderLogic.totalChunksCount) - } - Text { - id: chunksCount - text: "Progress: " + QrDecoderLogic.receivedChunksCount +"/"+QrDecoderLogic.totalChunksCount - } - } - - Component { - id: component - - Item { - anchors.fill: parent - - QRCodeReader { - id: qrCodeReader - - onCodeReaded: { - QrDecoderLogic.onDetectedQrCode(code) - } - - Component.onCompleted: { - qrCodeReader.setCameraSize(Qt.rect(loader.x, - loader.y, - loader.width, - loader.height)) - qrCodeReader.startReading() - } - Component.onDestruction: qrCodeReader.stopReading() - } - - } - - } - - -} diff --git a/client/ui/qml/Pages/PageServerConfiguringProgress.qml b/client/ui/qml/Pages/PageServerConfiguringProgress.qml deleted file mode 100644 index 04f1b6f5..00000000 --- a/client/ui/qml/Pages/PageServerConfiguringProgress.qml +++ /dev/null @@ -1,121 +0,0 @@ -import QtQuick -import QtQuick.Controls -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.ServerConfiguringProgress - logic: ServerConfiguringProgressLogic - - Caption { - id: caption - text: qsTr("Configuring...") - } - LabelType { - id: label - x: 0 - anchors.top: caption.bottom - anchors.topMargin: 10 - - width: parent.width - height: 31 - text: qsTr("Please wait.") - horizontalAlignment: Text.AlignHCenter - } - - LabelType { - id: labelServerBusy - x: 0 - anchors.top: label.bottom - anchors.topMargin: 30 - - anchors.horizontalCenter: parent.horizontalCenter - horizontalAlignment: Text.AlignHCenter - - width: parent.width - 40 - height: 41 - - text: ServerConfiguringProgressLogic.labelServerBusyText - visible: ServerConfiguringProgressLogic.labelServerBusyVisible - } - - LabelType { - anchors.bottom: pr.top - anchors.bottomMargin: 20 - - anchors.horizontalCenter: parent.horizontalCenter - horizontalAlignment: Text.AlignHCenter - - width: parent.width - 40 - height: 41 - text: ServerConfiguringProgressLogic.labelWaitInfoText - visible: ServerConfiguringProgressLogic.labelWaitInfoVisible - } - - - BlueButtonType { - id: pb_cancel - z: 1 - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: logo.bottom - anchors.bottomMargin: 40 - width: root.width - 60 - height: 40 - text: qsTr("Cancel") - visible: ServerConfiguringProgressLogic.pushButtonCancelVisible - enabled: ServerConfiguringProgressLogic.pushButtonCancelVisible - onClicked: { - ServerConfiguringProgressLogic.onPushButtonCancelClicked() - } - } - - ProgressBar { - id: pr - enabled: ServerConfiguringProgressLogic.pageEnabled - anchors.fill: pb_cancel - from: 0 - to: ServerConfiguringProgressLogic.progressBarMaximum - value: ServerConfiguringProgressLogic.progressBarValue - visible: ServerConfiguringProgressLogic.progressBarVisible - background: Rectangle { - implicitWidth: parent.width - implicitHeight: parent.height - color: "#100A44" - radius: 4 - } - - contentItem: Item { - implicitWidth: parent.width - implicitHeight: parent.height - Rectangle { - width: pr.visualPosition * parent.width - height: parent.height - radius: 4 - color: Qt.rgba(255, 255, 255, 0.15); - } - } - - LabelType { - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - text: ServerConfiguringProgressLogic.progressBarText - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#D4D4D4" - visible: ServerConfiguringProgressLogic.progressBarTextVisible - } - } - - Logo { - id : logo - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: parent.bottom - } -} diff --git a/client/ui/qml/Pages/PageServerContainers.qml b/client/ui/qml/Pages/PageServerContainers.qml deleted file mode 100644 index ddd607b0..00000000 --- a/client/ui/qml/Pages/PageServerContainers.qml +++ /dev/null @@ -1,434 +0,0 @@ -import QtQuick -import QtQuick.Controls -import Qt.labs.platform -import QtQuick.Layouts -import SortFilterProxyModel 0.2 -import ContainerProps 1.0 -import ProtocolProps 1.0 -import PageEnum 1.0 -import ProtocolEnum 1.0 -import "./" -import "../Controls" -import "../Config" -import "InstallSettings" - -PageBase { - id: root - page: PageEnum.ServerContainers - logic: ServerContainersLogic - - enabled: ServerContainersLogic.pageEnabled - - function resetPage() { - container_selector.selectedIndex = -1 - } - - Connections { - target: logic - function onUpdatePage() { - root.resetPage() - } - } - - BackButton { - id: back - onClicked: tb_c.currentIndex = -1 - } - Caption { - id: caption - text: container_selector.selectedIndex > 0 ? qsTr("Install new service") : qsTr("Installed services") - } - - SelectContainer { - id: container_selector - - onAboutToHide: { - pageLoader.focus = true - } - - onContainerSelected: function(c_index) { - var containerProto = ContainerProps.defaultProtocol(c_index) - - - if (ProtocolProps.defaultPort(containerProto) < 0) { - tf_port_num.enabled = false - tf_port_num.text = qsTr("Default") - } - else tf_port_num.text = ProtocolProps.defaultPort(containerProto) - cb_port_proto.currentIndex = ProtocolProps.defaultTransportProto(containerProto) - - tf_port_num.enabled = ProtocolProps.defaultPortChangeable(containerProto) - cb_port_proto.enabled = ProtocolProps.defaultTransportProtoChangeable(containerProto) - } - } - - Column { - id: c1 - visible: container_selector.selectedIndex > 0 - width: parent.width - anchors.top: caption.bottom - anchors.topMargin: 10 - - Caption { - font.pixelSize: 22 - text: UiLogic.containerName(container_selector.selectedIndex) - } - - Text { - width: parent.width - anchors.topMargin: 10 - padding: 10 - - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#181922" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.Wrap - - text: UiLogic.containerDesc(container_selector.selectedIndex) - } - } - - Rectangle { - id: frame_settings - visible: container_selector.selectedIndex > 0 - width: parent.width - anchors.top: c1.bottom - anchors.topMargin: 10 - - border.width: 1 - border.color: "lightgray" - anchors.bottomMargin: 5 - anchors.horizontalCenter: parent.horizontalCenter - radius: 2 - Grid { - id: grid - visible: container_selector.selectedIndex > 0 - anchors.fill: parent - columns: 2 - horizontalItemAlignment: Grid.AlignHCenter - verticalItemAlignment: Grid.AlignVCenter - topPadding: 5 - leftPadding: 10 - spacing: 5 - - - LabelType { - width: 130 - text: qsTr("Port") - } - TextFieldType { - id: tf_port_num - width: parent.width - 130 - parent.spacing - parent.leftPadding * 2 - } - LabelType { - width: 130 - text: qsTr("Network Protocol") - } - ComboBoxType { - id: cb_port_proto - width: parent.width - 130 - parent.spacing - parent.leftPadding * 2 - model: [ - qsTr("udp"), - qsTr("tcp"), - ] - } - } - } - - BlueButtonType { - id: pb_cancel_add - visible: container_selector.selectedIndex > 0 - - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: pb_continue_add.top - anchors.bottomMargin: 20 - - width: parent.width - 40 - height: 40 - text: qsTr("Cancel") - font.pixelSize: 16 - onClicked: container_selector.selectedIndex = -1 - - } - - BlueButtonType { - id: pb_continue_add - visible: container_selector.selectedIndex > 0 - - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: parent.bottom - anchors.bottomMargin: 20 - - width: parent.width - 40 - height: 40 - text: qsTr("Continue") - font.pixelSize: 16 - onClicked: { - let cont = container_selector.selectedIndex - let tp = ProtocolProps.transportProtoFromString(cb_port_proto.currentText) - let port = tf_port_num.text - ServerContainersLogic.onPushButtonContinueClicked(cont, port, tp) - } - } - - FlickableType { - visible: container_selector.selectedIndex <= 0 - clip: true - width: parent.width - anchors.top: caption.bottom - anchors.bottom: pb_add_container.top - contentHeight: col.height - - Column { - visible: container_selector.selectedIndex <= 0 - id: col - anchors { - left: parent.left; - right: parent.right; - } - spacing: 10 - - Caption { - id: cap1 - text: qsTr("Installed Protocols and Services") - leftPadding: -20 - font.pixelSize: 20 - - } - - SortFilterProxyModel { - id: proxyContainersModel - sourceModel: UiLogic.containersModel - filters: ValueFilter { - roleName: "is_installed_role" - value: true - } - } - - SortFilterProxyModel { - id: proxyProtocolsModel - sourceModel: UiLogic.protocolsModel - filters: ValueFilter { - roleName: "is_installed_role" - value: true - } - } - - - ListView { - id: tb_c - width: parent.width - 10 - height: tb_c.contentItem.height - currentIndex: -1 - spacing: 5 - clip: true - interactive: false - model: proxyContainersModel - - delegate: Item { - implicitWidth: tb_c.width - 10 - implicitHeight: c_item.height - Item { - id: c_item - width: parent.width - height: row_container.height + tb_p.height - anchors.left: parent.left - Rectangle { - anchors.top: parent.top - width: parent.width - height: 1 - color: "lightgray" - visible: index !== tb_c.currentIndex - } - Rectangle { - anchors.top: row_container.top - anchors.bottom: row_container.bottom - anchors.left: parent.left - anchors.right: parent.right - - color: "#63B4FB" - visible: index === tb_c.currentIndex - } - - RowLayout { - id: row_container - anchors.left: parent.left - anchors.right: parent.right - - Text { - id: lb_container_name - text: name_role - font.pixelSize: 17 - color: "#100A44" - topPadding: 16 - bottomPadding: 12 - leftPadding: 10 - verticalAlignment: Text.AlignVCenter - wrapMode: Text.WordWrap - Layout.fillWidth: true - - MouseArea { - enabled: col.visible - anchors.top: lb_container_name.top - anchors.bottom: lb_container_name.bottom - anchors.left: parent.left - anchors.right: parent.right - propagateComposedEvents: true - onClicked: { - if (tb_c.currentIndex === index) tb_c.currentIndex = -1 - else tb_c.currentIndex = index - - UiLogic.protocolsModel.setSelectedDockerContainer(proxyContainersModel.mapToSource(index)) - } - } - } - - ImageButtonType { - id: button_remove - visible: (index === tb_c.currentIndex) && ServerContainersLogic.isManagedServer - Layout.alignment: Qt.AlignRight - checkable: true - icon.source: "qrc:/images/delete.png" - implicitWidth: 30 - implicitHeight: 30 - - checked: default_role - onClicked: popupRemove.open() - - VisibleBehavior on visible { } - } - - PopupWithQuestion { - id: popupRemove - questionText: qsTr("Remove container") + " " + name_role + "?" + "\n" + qsTr("This action will erase all data of this container on the server.") - yesFunc: function() { - tb_c.currentIndex = -1 - ServerContainersLogic.onPushButtonRemoveClicked(proxyContainersModel.mapToSource(index)) - close() - } - noFunc: function() { - close() - } - } - - ImageButtonType { - id: button_share - visible: (index === tb_c.currentIndex) && ServerContainersLogic.isManagedServer - Layout.alignment: Qt.AlignRight - icon.source: "qrc:/images/share.png" - implicitWidth: 30 - implicitHeight: 30 - onClicked: { - ServerContainersLogic.onPushButtonShareClicked(proxyContainersModel.mapToSource(index)) - } - - VisibleBehavior on visible { } - } - - ImageButtonType { - id: button_default - visible: service_type_role == ProtocolEnum.Vpn - - Layout.alignment: Qt.AlignRight - checkable: true - img.source: checked ? "qrc:/images/check.png" : "qrc:/images/uncheck.png" - implicitWidth: 30 - implicitHeight: 30 - - checked: default_role - onClicked: { - ServerContainersLogic.onPushButtonDefaultClicked(proxyContainersModel.mapToSource(index)) - } - } - } - - - ListView { - id: tb_p - currentIndex: -1 - x: 10 - anchors.top: row_container.bottom - - width: parent.width - 40 - height: index === tb_c.currentIndex ? tb_p.contentItem.height : 0 - implicitHeight: height - - spacing: 0 - clip: true - interactive: false - model: proxyProtocolsModel - - - Behavior on height { - NumberAnimation { - duration: 200 - } - } - - delegate: Item { - id: dp_item - - implicitWidth: tb_p.width - 10 - implicitHeight: p_item.height - Item { - id: p_item - width: parent.width - height: lb_protocol_name.height - anchors.left: parent.left - Rectangle { - anchors.top: parent.top - width: parent.width - height: 1 - color: "lightgray" - visible: index > 0 - } - - SettingButtonType { - id: lb_protocol_name - topPadding: 10 - bottomPadding: 10 - - anchors.left: parent.left - anchors.leftMargin: 10 - - width: parent.width - height: 45 - text: qsTr(name_role + " settings") - textItem.font.pixelSize: 16 - icon.source: "qrc:/images/settings.png" - onClicked: { - tb_p.currentIndex = index - ServerContainersLogic.onPushButtonProtoSettingsClicked( - proxyContainersModel.mapToSource(tb_c.currentIndex), - proxyProtocolsModel.mapToSource(tb_p.currentIndex)) - } - } - } - } - } - } - } - } - } - } - - - BlueButtonType { - id: pb_add_container - visible: container_selector.selectedIndex < 0 && ServerContainersLogic.isManagedServer - - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: parent.bottom - anchors.topMargin: 10 - anchors.bottomMargin: 20 - - width: parent.width - 40 - height: 40 - text: qsTr("Install new service") - font.pixelSize: 16 - onClicked: container_selector.visible ? container_selector.close() : container_selector.open() - } -} diff --git a/client/ui/qml/Pages/PageServerList.qml b/client/ui/qml/Pages/PageServerList.qml deleted file mode 100644 index 80ac9a1b..00000000 --- a/client/ui/qml/Pages/PageServerList.qml +++ /dev/null @@ -1,185 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Shapes 1.4 -import PageEnum 1.0 -import "../Controls" -import "./" -import "../Config" - -PageBase { - id: root - page: PageEnum.ServersList - logic: ServerListLogic - - BackButton { - id: back - } - Caption { - id: caption - text: qsTr("Servers") - width: undefined - } - - SvgButtonType { - anchors.verticalCenter: caption.verticalCenter - anchors.leftMargin: 10 - anchors.left: caption.right - width: 27 - height: 27 - - icon.source: "qrc:/images/svg/control_point_black_24dp.svg" - onClicked: { - UiLogic.goToPage(PageEnum.Start); - } - } - - ListView { - id: listWidget_servers - x: GC.defaultMargin - anchors.top: caption.bottom - anchors.topMargin: 15 - width: parent.width - GC.defaultMargin - 1 - anchors.bottom: parent.bottom - anchors.bottomMargin: 20 - model: ServerListLogic.serverListModel - highlightRangeMode: ListView.ApplyRange - highlightMoveVelocity: -1 - currentIndex: ServerListLogic.currServerIdx - spacing: 5 - clip: true - delegate: Item { - height: 60 - width: listWidget_servers.width - 15 - MouseArea { - id: ms - anchors.fill: parent - hoverEnabled: true - onClicked: { - if (GC.isMobile()) { - ServerListLogic.onServerListPushbuttonSettingsClicked(index) - } - mouse.accepted = false - } - onEntered: { - mouseExitAni.stop() - mouseEnterAni.start() - } - onExited: { - mouseEnterAni.stop() - mouseExitAni.start() - } - } - Rectangle { - anchors.fill: parent - gradient: ms.containsMouse ? gradient_containsMouse : gradient_notContainsMouse - LinearGradient { - id: gradient_notContainsMouse - x1: 0 ; y1:0 - x2: 0 ; y2: height - stops: [ - GradientStop { position: 0.0; color: "#FAFBFE" }, - GradientStop { position: 1.0; color: "#ECEEFF" } - ] - } - LinearGradient { - id: gradient_containsMouse - x1: 0 ; y1:0 - x2: 0 ; y2: height - stops: [ - GradientStop { position: 0.0; color: "#FAFBFE" }, - GradientStop { position: 1.0; color: "#DCDEDF" } - ] - } - } - - LabelType { - id: label_address - x: 20 - y: 40 - width: listWidget_servers.width - 100 - height: 16 - text: address - } - Text { - x: 10 - y: 10 - width: listWidget_servers.width - 100 - height: 21 - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - font.bold: true - color: "#181922" - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - wrapMode: Text.Wrap - text: desc - } - ImageButtonType { - x: parent.width - 30 - y: 15 - width: 30 - height: 30 - checkable: true - icon.source: checked ? "qrc:/images/check.png" - : "qrc:/images/uncheck.png" - onClicked: { - ServerListLogic.onServerListPushbuttonDefaultClicked(index) - } - checked: is_default - enabled: !is_default - } - SvgButtonType { - id: pushButtonSetting - x: parent.width - 70 - y: 15 - width: 30 - height: 30 - icon.source: "qrc:/images/svg/settings_black_24dp.svg" - opacity: 0 - - OpacityAnimator { - id: mouseEnterAni - target: pushButtonSetting; - from: 0; - to: 1; - duration: 150 - running: false - easing.type: Easing.InOutQuad - } - OpacityAnimator { - id: mouseExitAni - target: pushButtonSetting; - from: 1; - to: 0; - duration: 150 - running: false - easing.type: Easing.InOutQuad - } - MouseArea { - cursorShape: Qt.PointingHandCursor - anchors.fill: parent - hoverEnabled: true - propagateComposedEvents: true - - onEntered: { - mouseExitAni.stop() - mouseEnterAni.start() - } - onExited: { - mouseEnterAni.stop() - mouseExitAni.start() - } - - onClicked: { - ServerListLogic.onServerListPushbuttonSettingsClicked(index) - } - } - } - } - - ScrollBar.vertical: ScrollBar { - policy: ScrollBar.AsNeeded - } - } -} diff --git a/client/ui/qml/Pages/PageServerSettings.qml b/client/ui/qml/Pages/PageServerSettings.qml deleted file mode 100644 index 4ef22ce4..00000000 --- a/client/ui/qml/Pages/PageServerSettings.qml +++ /dev/null @@ -1,140 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.ServerSettings - logic: ServerSettingsLogic - - enabled: ServerSettingsLogic.pageEnabled - - BackButton { - id: back - } - - Caption { - id: caption - text: qsTr("Server settings") - anchors.horizontalCenter: parent.horizontalCenter - } - - FlickableType { - id: fl - anchors.top: caption.bottom - anchors.bottom: logo.top - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - LabelType { - Layout.fillWidth: true - font.pixelSize: 20 - horizontalAlignment: Text.AlignHCenter - text: ServerSettingsLogic.labelCurrentVpnProtocolText - } - - TextFieldType { - Layout.fillWidth: true - font.pixelSize: 20 - horizontalAlignment: Text.AlignHCenter - text: ServerSettingsLogic.labelServerText - readOnly: true - background: Item {} - } - - LabelType { - Layout.fillWidth: true - text: ServerSettingsLogic.labelWaitInfoText - visible: ServerSettingsLogic.labelWaitInfoVisible - } - TextFieldType { - Layout.fillWidth: true - text: ServerSettingsLogic.lineEditDescriptionText - onEditingFinished: { - ServerSettingsLogic.lineEditDescriptionText = text - ServerSettingsLogic.onLineEditDescriptionEditingFinished() - } - } - - BlueButtonType { - text: qsTr("Protocols and Services") - Layout.topMargin: 20 - Layout.fillWidth: true - onClicked: { - UiLogic.goToPage(PageEnum.ServerContainers) - } - } - - BlueButtonType { - Layout.fillWidth: true - Layout.topMargin: 10 - text: qsTr("Share Server (FULL ACCESS)") - visible: ServerSettingsLogic.pushButtonShareFullVisible - onClicked: { - ServerSettingsLogic.onPushButtonShareFullClicked() - } - } - - BlueButtonType { - Layout.fillWidth: true - Layout.topMargin: 10 - text: qsTr("Advanced server settings") - onClicked: { - UiLogic.goToPage(PageEnum.AdvancedServerSettings) - } - } - - BlueButtonType { - Layout.fillWidth: true - Layout.topMargin: 60 - text: ServerSettingsLogic.pushButtonClearClientCacheText - visible: ServerSettingsLogic.pushButtonClearClientCacheVisible - onClicked: { - ServerSettingsLogic.onPushButtonClearClientCacheClicked() - } - } - - BlueButtonType { - Layout.fillWidth: true - Layout.topMargin: 10 - text: qsTr("Forget this server") - onClicked: { - if (ServerSettingsLogic.isCurrentServerHasCredentials()) { - popupForgetServer.questionText = "Attention! This action will not remove any data from the server, it will just remove server from the list. Continue?" - } - else { - popupForgetServer.questionText = "Remove server from the list?" - } - popupForgetServer.open() - } - } - - PopupWithQuestion { - id: popupForgetServer - yesFunc: function() { - ServerSettingsLogic.onPushButtonForgetServer() - close() - } - noFunc: function() { - close() - } - } - } - } - - Logo { - id : logo - anchors.bottom: parent.bottom - } -} diff --git a/client/ui/qml/Pages/PageSetupWizard.qml b/client/ui/qml/Pages/PageSetupWizard.qml deleted file mode 100644 index a7e93a3a..00000000 --- a/client/ui/qml/Pages/PageSetupWizard.qml +++ /dev/null @@ -1,108 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.Wizard - logic: WizardLogic - - BackButton { - id: back_from_setup_wizard - } - Caption { - id: caption - text: qsTr("Setup your server to use VPN") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - anchors.bottom: next_button.top - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - RadioButtonType { - id: radioButton_setup_wizard_high - Layout.fillWidth: true - text: qsTr("High censorship level") - checked: WizardLogic.radioButtonHighChecked - onCheckedChanged: { - WizardLogic.radioButtonHighChecked = checked - } - } - LabelType { - Layout.fillWidth: true - Layout.leftMargin: 25 - verticalAlignment: Text.AlignTop - text: qsTr("I'm living in a country with a high censorship level. Many of the foreign websites and VPNs are blocked by my government. I want to setup a reliable VPN, which can not be detected by my internet provider and my government. -OpenVPN and ShadowSocks over Cloak (VPN obfuscation) profiles will be installed.\n") - } - - - RadioButtonType { - id: radioButton_setup_wizard_medium - Layout.fillWidth: true - text: qsTr("Medium censorship level") - checked: WizardLogic.radioButtonMediumChecked - onCheckedChanged: { - WizardLogic.radioButtonMediumChecked = checked - } - } - LabelType { - Layout.fillWidth: true - Layout.leftMargin: 25 - verticalAlignment: Text.AlignTop - text: qsTr("I'm living in a country with a medium censorship level. Some websites are blocked by my government, but VPNs are not blocked at all. I want to setup a flexible solution. -OpenVPN over ShadowSocks profile will be installed.\n") - } - - - RadioButtonType { - id: radioButton_setup_wizard_low - Layout.fillWidth: true - text: qsTr("Low censorship level") - checked: WizardLogic.radioButtonLowChecked - onCheckedChanged: { - WizardLogic.radioButtonLowChecked = checked - } - } - LabelType { - Layout.fillWidth: true - Layout.leftMargin: 25 - verticalAlignment: Text.AlignTop - text: qsTr("I want to improve my privacy on the internet. -OpenVPN profile will be installed.\n") - } - } - } - - BlueButtonType { - id: next_button - anchors.bottom: parent.bottom - anchors.bottomMargin: GC.defaultMargin - x: GC.defaultMargin - width: parent.width - 2 * GC.defaultMargin - text: qsTr("Next") - onClicked: { - if (radioButton_setup_wizard_high.checked) { - UiLogic.goToPage(PageEnum.WizardHigh, false); - } else if (radioButton_setup_wizard_medium.checked) { - UiLogic.goToPage(PageEnum.WizardMedium, false); - } else if (radioButton_setup_wizard_low.checked) { - UiLogic.goToPage(PageEnum.WizardLow, false); - } - } - } -} diff --git a/client/ui/qml/Pages/PageSetupWizardHighLevel.qml b/client/ui/qml/Pages/PageSetupWizardHighLevel.qml deleted file mode 100644 index e0f194ff..00000000 --- a/client/ui/qml/Pages/PageSetupWizardHighLevel.qml +++ /dev/null @@ -1,96 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.WizardHigh - logic: WizardLogic - - BackButton { - id: back_from_setup_wizard - } - Caption { - id: caption - text: qsTr("Setup Wizard") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - anchors.bottom: next_button.top - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - LabelType { - Layout.fillWidth: true - verticalAlignment: Text.AlignTop - text: qsTr("AmneziaVPN will install a VPN protocol which is not visible to your internet provider and government firewall. Your VPN connection will be seen by your internet provider as regular web traffic to a particular website. - -You SHOULD set this website address to some foreign website which is not blocked by your internet provider. In other words, you need to type some foreign website address which is accessible to you without a VPN.") - } - - LabelType { - Layout.fillWidth: true - Layout.topMargin: 15 - verticalAlignment: Text.AlignTop - text: qsTr("Type another web site address for masking or keep it by default. Your internet provider will think you working on this web site when you connected to VPN.") - } - - TextFieldType { - id: website_masking - Layout.fillWidth: true - text: WizardLogic.lineEditHighWebsiteMaskingText - onEditingFinished: { - let _text = website_masking.text - _text = _text.replace("http://", ""); - _text = _text.replace("https://", ""); - if (!_text) { - return - } - _text = _text.split("/")[0]; - WizardLogic.lineEditHighWebsiteMaskingText = _text - } - onAccepted: { - next_button.clicked() - } - } - - LabelType { - Layout.fillWidth: true - Layout.topMargin: 15 - verticalAlignment: Text.AlignTop - text: qsTr("OpenVPN and ShadowSocks over Cloak (VPN obfuscation) profiles will be installed. - -This protocol support exporting connection profiles to mobile devices by exporting ShadowSocks and Cloak configs (you should launch the 3rd party open source VPN client - ShadowSocks VPN and install Cloak plugin).") - } - } - } - - BlueButtonType { - id: next_button - anchors.bottom: parent.bottom - anchors.bottomMargin: GC.defaultMargin - x: GC.defaultMargin - width: parent.width - 2 * GC.defaultMargin - text: qsTr("Next") - onClicked: { - let domain = website_masking.text; - if (!domain || !domain.includes(".")) { - return - } - UiLogic.goToPage(PageEnum.WizardVpnMode, false) - } - } -} diff --git a/client/ui/qml/Pages/PageSetupWizardLowLevel.qml b/client/ui/qml/Pages/PageSetupWizardLowLevel.qml deleted file mode 100644 index d4e590c1..00000000 --- a/client/ui/qml/Pages/PageSetupWizardLowLevel.qml +++ /dev/null @@ -1,65 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.WizardLow - logic: WizardLogic - - BackButton { - id: back_from_setup_wizard - } - Caption { - id: caption - text: qsTr("Setup Wizard") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - anchors.bottom: next_button.top - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - LabelType { - Layout.fillWidth: true - verticalAlignment: Text.AlignTop - text: qsTr('AmneziaVPN will install the OpenVPN protocol with public/private key pairs generated on both server and client sides. - -You can also configure the connection on your mobile device by copying the exported ".ovpn" file to your device, and setting up the official OpenVPN client. - -We recommend not to use messaging applications for sending the connection profile - it contains VPN private keys.') - } - LabelType { - Layout.fillWidth: true - Layout.topMargin: 15 - text: qsTr('OpenVPN profile will be installed') - verticalAlignment: Text.AlignBottom - } - } - } - - BlueButtonType { - id: next_button - anchors.bottom: parent.bottom - anchors.bottomMargin: GC.defaultMargin - x: GC.defaultMargin - width: parent.width - 2 * GC.defaultMargin - text: qsTr("Start configuring") - onClicked: { - WizardLogic.onPushButtonLowFinishClicked() - } - } -} diff --git a/client/ui/qml/Pages/PageSetupWizardMediumLevel.qml b/client/ui/qml/Pages/PageSetupWizardMediumLevel.qml deleted file mode 100644 index 6e1a45c1..00000000 --- a/client/ui/qml/Pages/PageSetupWizardMediumLevel.qml +++ /dev/null @@ -1,60 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.WizardMedium - logic: WizardLogic - - BackButton { - id: back_from_setup_wizard - } - Caption { - id: caption - text: qsTr("Setup Wizard") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - anchors.bottom: next_button.top - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - LabelType { - Layout.fillWidth: true - verticalAlignment: Text.AlignTop - text: qsTr('AmneziaVPN will install a VPN protocol which is difficult to detect by your internet provider and government firewall (but possible). In most cases, this is the most suitable protocol. This protocol is faster compared to the VPN protocols with "VPN masking".\n\nThis protocol supports exporting connection profiles to mobile devices by using QR codes (you should launch the 3rd party open source VPN client - ShadowSocks VPN).') - } - LabelType { - Layout.fillWidth: true - Layout.topMargin: 15 - text: qsTr('OpenVPN over ShadowSocks profile will be installed') - } - } - } - - BlueButtonType { - id: next_button - anchors.bottom: parent.bottom - anchors.bottomMargin: GC.defaultMargin - x: GC.defaultMargin - width: parent.width - 2 * GC.defaultMargin - text: qsTr("Next") - onClicked: { - UiLogic.goToPage(PageEnum.WizardVpnMode, false) - } - } -} diff --git a/client/ui/qml/Pages/PageSetupWizardVPNMode.qml b/client/ui/qml/Pages/PageSetupWizardVPNMode.qml deleted file mode 100644 index 497ccad8..00000000 --- a/client/ui/qml/Pages/PageSetupWizardVPNMode.qml +++ /dev/null @@ -1,65 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.WizardVpnMode - logic: WizardLogic - - BackButton { - id: back_from_setup_wizard - } - Caption { - id: caption - text: qsTr("Setup Wizard") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - anchors.bottom: vpn_mode_finish.top - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - LabelType { - Layout.fillWidth: true - verticalAlignment: Text.AlignTop - text: qsTr('Optional.\n -You can enable VPN mode "For selected sites" and add blocked sites you need to visit manually. If you will choose this option, you will need add every blocked site you want to visit to the access list. You may switch between modes later.\n\nPlease note, you should add addresses to the list after VPN connection established. You may add any domain, URL or IP address, it will be resolved to IP address.') - } - - CheckBoxType { - Layout.fillWidth: true - text: qsTr('Turn on mode "VPN for selected sites"') - checked: WizardLogic.checkBoxVpnModeChecked - onCheckedChanged: { - WizardLogic.checkBoxVpnModeChecked = checked - } - } - } - } - - BlueButtonType { - id: vpn_mode_finish - anchors.bottom: parent.bottom - anchors.bottomMargin: GC.defaultMargin - x: GC.defaultMargin - width: parent.width - 2 * GC.defaultMargin - text: qsTr("Start configuring") - onClicked: { - WizardLogic.onPushButtonVpnModeFinishClicked() - } - } -} diff --git a/client/ui/qml/Pages/PageShareConnection.qml b/client/ui/qml/Pages/PageShareConnection.qml deleted file mode 100644 index b5439b47..00000000 --- a/client/ui/qml/Pages/PageShareConnection.qml +++ /dev/null @@ -1,87 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Dialogs -import QtQuick.Layouts -import SortFilterProxyModel 0.2 -import ContainerProps 1.0 -import ProtocolProps 1.0 -import PageEnum 1.0 -import ProtocolEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.ShareConnection - logic: ShareConnectionLogic - - BackButton { - id: back - } - - Caption { - id: caption - text: qsTr("Share protocol config") - width: undefined - } - - - FlickableType { - clip: true - anchors.top: caption.bottom - contentHeight: col.height - boundsBehavior: Flickable.StopAtBounds - - Column { - id: col - anchors { - left: parent.left; - right: parent.right; - } - topPadding: 20 - spacing: 10 - - SortFilterProxyModel { - id: proxyProtocolsModel - sourceModel: UiLogic.protocolsModel - filters: ValueFilter { - roleName: "is_installed_role" - value: true - } - } - - - ShareConnectionContent { - text: qsTr("Share for Amnezia") - height: 40 - width: tb_c.width - 10 - onClicked: UiLogic.goToShareProtocolPage(ProtocolEnum.Any) - } - - ListView { - id: tb_c - width: parent.width - 10 - height: tb_c.contentItem.height - currentIndex: -1 - spacing: 10 - clip: true - interactive: false - model: proxyProtocolsModel - - delegate: Item { - implicitWidth: tb_c.width - 10 - implicitHeight: c_item.height - - ShareConnectionContent { - id: c_item - text: qsTr("Share for ") + name_role - height: 40 - width: tb_c.width - 10 - onClicked: UiLogic.goToShareProtocolPage(proxyProtocolsModel.mapToSource(index)) - } - } - } - } - } -} diff --git a/client/ui/qml/Pages/PageSites.qml b/client/ui/qml/Pages/PageSites.qml deleted file mode 100644 index 673e7e7a..00000000 --- a/client/ui/qml/Pages/PageSites.qml +++ /dev/null @@ -1,315 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQml.Models -import Qt.labs.platform -import QtQuick.Dialogs -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.Sites - logic: SitesLogic - - property int lastIndex: 0 - - BackButton { - id: back - } - - Caption { - id: caption - text: SitesLogic.labelSitesAddCustomText - } - - LabelType { - id: lb_addr - color: "#333333" - text: qsTr("Web site/Hostname/IP address/Subnet") - x: 20 - anchors.top: caption.bottom - anchors.topMargin: 10 - width: parent.width - height: 21 - } - - TextFieldType { - anchors.top: lb_addr.bottom - anchors.topMargin: 10 - anchors.left: parent.left - anchors.leftMargin: 20 - anchors.right: sites_add.left - anchors.rightMargin: 10 - height: 31 - placeholderText: qsTr("yousite.com or IP address") - text: SitesLogic.lineEditSitesAddCustomText - onEditingFinished: { - SitesLogic.lineEditSitesAddCustomText = text - } - onAccepted: { - SitesLogic.onPushButtonAddCustomSitesClicked() - } - } - - BlueButtonType { - id: sites_add - anchors.right: sites_import.left - anchors.rightMargin: 10 - anchors.top: lb_addr.bottom - anchors.topMargin: 10 - width: 51 - height: 31 - font.pixelSize: 24 - text: "+" - onClicked: { - SitesLogic.onPushButtonAddCustomSitesClicked() - } - } - - BasicButtonType { - id: sites_import - anchors.right: parent.right - anchors.rightMargin: 20 - anchors.top: lb_addr.bottom - anchors.topMargin: 10 - width: 51 - height: 31 - background: Rectangle { - anchors.fill: parent - radius: 4 - color: parent.containsMouse ? "#211966" : "#100A44" - } - font.pixelSize: 16 - contentItem: Item { - anchors.fill: parent - Image { - anchors.centerIn: parent - width: 20 - height: 20 - source: "qrc:/images/folder.png" - fillMode: Image.Stretch - } - } - antialiasing: true - onClicked: { - fileDialog.open() - } - } - FileDialog { - id: fileDialog - title: qsTr("Import IP addresses") - visible: false - currentFolder: StandardPaths.writableLocation(StandardPaths.DocumentsLocation) - onAccepted: { - SitesLogic.onPushButtonSitesImportClicked(fileUrl) - } - } - - DelegateModel { - id: visualModel - model: SitesLogic.tableViewSitesModel - groups: [ - DelegateModelGroup { - id : delegateModelGroup - name: "multiSelect" - function removeAll(){ - var count = delegateModelGroup.count; - if (count !== 0){ - delegateModelGroup.remove(0,count); - } - } - function selectAll(){ - for(var i = 0; i < visualModel.count; i++){ - visualModel.items.get(i).inMultiSelect = true - } - } - } - ] - delegate: Rectangle { - id: item - focus: true - height: 25 - width: root.width - color: item.DelegateModel.inMultiSelect ? '#63b4fb' : 'transparent' - implicitWidth: 170 * 2 - implicitHeight: 30 - Item { - width: 170 - height: 30 - anchors.left: parent.left - id: c1 - Rectangle { - anchors.top: parent.top - width: parent.width - height: 1 - color: "lightgray" - visible: index !== tb.currentRow - } - Rectangle { - anchors.fill: parent - color: "#63B4FB" - visible: index === tb.currentRow - - } - Text { - text: url_path - anchors.fill: parent - leftPadding: 10 - verticalAlignment: Text.AlignVCenter - } - } - Item { - anchors.left: c1.right - width: 170 - height: 30 - Rectangle { - anchors.top: parent.top - width: parent.width - height: 1 - color: "lightgray" - visible: index !== tb.currentRow - } - Rectangle { - anchors.fill: parent - color: "#63B4FB" - visible: index === tb.currentRow - - } - Text { - text: ip - anchors.fill: parent - leftPadding: 10 - verticalAlignment: Text.AlignVCenter - } - } - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.LeftButton | Qt.RightButton - onClicked:{ - tb.focus = true - if(mouse.button === Qt.RightButton){ - //copyPasteMenu.popup() - console.log("RightButton") - } - if(mouse.button === Qt.LeftButton){ - switch(mouse.modifiers){ - case Qt.ControlModifier : - item.DelegateModel.inMultiSelect = !item.DelegateModel.inMultiSelect - break; - case Qt.ShiftModifier : - delegateModelGroup.removeAll(); - var start = lastIndex <= index? lastIndex: index; - var end = lastIndex >= index? lastIndex: index; - for(var i = start;i <= end;i++){ - visualModel.items.get(i).inMultiSelect = true - } - break; - default: - delegateModelGroup.removeAll(); - item.DelegateModel.inMultiSelect = true - lastIndex = index - break; - } - } - } - } - } - } - - ListView { - id: tb - x: 20 - anchors.top: sites_add.bottom - anchors.topMargin: 10 - width: parent.width - 40 - anchors.bottom: sites_delete.top - anchors.bottomMargin: 10 - spacing: 1 - clip: true - focus: true - activeFocusOnTab: true - keyNavigationEnabled: true - property int currentRow: -1 - model: visualModel - - Keys.onPressed: { - if (event.key === Qt.Key_PageUp) { - let idx = tb.indexAt(1, tb.contentY) - tb.positionViewAtIndex(idx-20, ListView.Beginning) - event.accepted = true - } - else if (event.key === Qt.Key_PageDown) { - let idx = tb.indexAt(1, tb.contentY) - tb.positionViewAtIndex(idx+20, ListView.Beginning) - event.accepted = true - } - else if (event.key === Qt.Key_Home) { - tb.positionViewAtBeginning() - event.accepted = true - } - else if (event.key === Qt.Key_End) { - tb.positionViewAtEnd() - event.accepted = true - } - else if (event.key === Qt.Key_Delete) { - let items = [] - for(let i = 0; i < visualModel.count; i++){ - if (visualModel.items.get(i).inMultiSelect) items.push(i) - } - SitesLogic.onPushButtonSitesDeleteClicked(items) - event.accepted = true - } - else if (event.key === Qt.Key_A) { - delegateModelGroup.selectAll() - event.accepted = true - } - } - - } - - BlueButtonType { - id: sites_delete - anchors.bottom: select_all.top - anchors.bottomMargin: 10 - anchors.horizontalCenter: parent.horizontalCenter - height: 31 - font.pixelSize: 16 - text: qsTr("Delete selected") - onClicked: { - let items = [] - for(let i = 0; i < visualModel.count; i++){ - if (visualModel.items.get(i).inMultiSelect) items.push(i) - } - - SitesLogic.onPushButtonSitesDeleteClicked(items) - } - } - - BlueButtonType { - id: select_all - anchors.bottom: sites_export.top - anchors.bottomMargin: 10 - anchors.horizontalCenter: parent.horizontalCenter - height: 31 - font.pixelSize: 16 - text: qsTr("Select all") - onClicked: { - delegateModelGroup.selectAll() - } - } - - BlueButtonType { - id: sites_export - anchors.bottom: parent.bottom - anchors.bottomMargin: 20 - anchors.horizontalCenter: parent.horizontalCenter - height: 31 - font.pixelSize: 16 - text: qsTr("Export all") - onClicked: { - SitesLogic.onPushButtonSitesExportClicked() - } - } -} diff --git a/client/ui/qml/Pages/PageStart.qml b/client/ui/qml/Pages/PageStart.qml deleted file mode 100644 index a752817f..00000000 --- a/client/ui/qml/Pages/PageStart.qml +++ /dev/null @@ -1,356 +0,0 @@ -import QtQuick -import QtQuick.Controls -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.Start - logic: StartPageLogic - - Connections { - target: StartPageLogic - - function onShowPassphraseRequestMessage() { - popupWithTextField.open() - } - } - - BackButton { - id: back_from_start - visible: pageLoader.depth > 1 - } - - ImageButtonType { - anchors { - right: parent.right - top: parent.top - } - - width: 41 - height: 41 - imgMarginHover: 8 - imgMargin: 9 - icon.source: "qrc:/images/settings_grey.png" - visible: !GeneralSettingsLogic.existsAnyServer - onClicked: { - UiLogic.goToPage(PageEnum.GeneralSettings) - } - } - - Caption { - id: caption - text: start_switch_page.checked ? - qsTr("Setup your server to use VPN") : - qsTr("Connect to the already created VPN server") - } - - Logo { - id: logo - anchors.bottom: parent.bottom - } - - BasicButtonType { - id: start_switch_page - width: parent.width - 2 * GC.defaultMargin - implicitHeight: 40 - - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: logo.top - anchors.bottomMargin: 10 - anchors.topMargin: 20 - - text: qsTr("Set up your own server") - checked: false - checkable: true - onCheckedChanged: { - if (checked) { - page_start_new_server.visible = true - page_start_import.visible = false - text = qsTr("Import connection"); - } - else { - page_start_new_server.visible = false - page_start_import.visible = true - text = qsTr("Set up your own server"); - } - } - - background: Rectangle { - anchors.fill: parent - border.width: 1 - border.color: "#211C4A" - radius: 4 - } - - contentItem: Text { - anchors.fill: parent - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#100A44" - text: start_switch_page.text - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - antialiasing: true - } - - Item { - id: page_start_import - width: parent.width - anchors.top: caption.bottom - anchors.bottom: start_switch_page.top - anchors.bottomMargin: 10 - - visible: true - - LabelType { - id: label_connection_code - anchors.top: parent.top - anchors.topMargin: 20 - anchors.horizontalCenter: parent.horizontalCenter - text: qsTr("Connection code") - } - TextFieldType { - id: lineEdit_start_existing_code - anchors.top: label_connection_code.bottom - anchors.horizontalCenter: parent.horizontalCenter - placeholderText: "vpn://..." - text: StartPageLogic.lineEditStartExistingCodeText - onEditingFinished: { - StartPageLogic.lineEditStartExistingCodeText = text - } - } - BlueButtonType { - id: new_sever_import - anchors.horizontalCenter: parent.horizontalCenter - y: 210 - anchors.top: lineEdit_start_existing_code.bottom - anchors.topMargin: 10 - text: qsTr("Connect") - onClicked: { - StartPageLogic.onPushButtonImport() - } - } - - - BlueButtonType { - id: qr_code_import_open - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: new_sever_import.bottom - anchors.topMargin: 40 - - text: qsTr("Open file") - onClicked: { - StartPageLogic.onPushButtonImportOpenFile() - } - enabled: StartPageLogic.pushButtonConnectEnabled - } - - BlueButtonType { - id: qr_code_import - visible: GC.isMobile() - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: qr_code_import_open.bottom - anchors.topMargin: 10 - - text: qsTr("Scan QR code") - onClicked: { - if (Qt.platform.os === "ios") { - UiLogic.goToPage(PageEnum.QrDecoderIos) - } else { - StartPageLogic.startQrDecoder() - } - } - enabled: StartPageLogic.pushButtonConnectEnabled - } - - BlueButtonType { - id: btn_restore_cfg - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: qr_code_import.bottom - anchors.topMargin: 30 - visible: UiLogic.pagesStackDepth === 1 - enabled: StartPageLogic.pushButtonConnectEnabled - - text: qsTr("Restore app config") - onClicked: { - AppSettingsLogic.onPushButtonRestoreAppConfigClicked() - } - } - } - - - Item { - id: page_start_new_server - width: parent.width - anchors.top: caption.bottom - anchors.bottom: start_switch_page.top - anchors.bottomMargin: 10 - - visible: false - - BasicButtonType { - id: new_sever_get_info - width: parent.width - 80 - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: parent.top - anchors.topMargin: 5 - - text: qsTr("How to get own server? →") - background: Item { - anchors.fill: parent - } - - contentItem: Text { - anchors.fill: parent - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#15CDCB"; - text: new_sever_get_info.text - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - antialiasing: true - checkable: true - checked: true - onClicked: { - Qt.openUrlExternally("https://amnezia.org/instruction.html") - } - } - LabelType { - id: label_server_ip - x: 40 - anchors.top: new_sever_get_info.bottom - text: qsTr("Server IP address [:port]") - } - TextFieldType { - id: new_server_ip - anchors.top: label_server_ip.bottom - anchors.horizontalCenter: parent.horizontalCenter - text: StartPageLogic.lineEditIpText - onEditingFinished: { StartPageLogic.lineEditIpText = text } - onTextEdited: { StartPageLogic.lineEditIpText = text } - - validator: RegularExpressionValidator { - regularExpression: StartPageLogic.ipAddressPortRegex - } - } - - LabelType { - id:label_login - x: 40 - anchors.top: new_server_ip.bottom - text: qsTr("Login to connect via SSH") - } - TextFieldType { - id: new_server_login - anchors.top: label_login.bottom - anchors.horizontalCenter: parent.horizontalCenter - text: StartPageLogic.lineEditLoginText - onEditingFinished: { StartPageLogic.lineEditLoginText = text } - onTextEdited: { StartPageLogic.lineEditLoginText = text } - } - - LabelType { - id: label_new_server_password - x: 40 - anchors.top: new_server_login.bottom - text: qsTr("Password") - } - TextFieldType { - id: new_server_password - anchors.top: label_new_server_password.bottom - anchors.horizontalCenter: parent.horizontalCenter - - inputMethodHints: Qt.ImhSensitiveData - echoMode: TextInput.Password - text: StartPageLogic.lineEditPasswordText - onEditingFinished: { StartPageLogic.lineEditPasswordText = text } - onTextEdited: { StartPageLogic.lineEditPasswordText = text } - onAccepted: { StartPageLogic.onPushButtonConnect() } - } - TextFieldType { - id: new_server_ssh_key - anchors.top: label_new_server_password.bottom - anchors.horizontalCenter: parent.horizontalCenter - - visible: false - height: 71 - font.pixelSize: 10 - verticalAlignment: Text.AlignTop - inputMethodHints: Qt.ImhSensitiveData - - text: StartPageLogic.textEditSshKeyText - onEditingFinished: { StartPageLogic.textEditSshKeyText = text } - onTextEdited: { StartPageLogic.textEditSshKeyText = text } - onAccepted: { StartPageLogic.onPushButtonConnect() } - } - - LabelType { - x: 40 - y: 390 - width: 301 - height: 41 - text: StartPageLogic.labelWaitInfoText - visible: StartPageLogic.labelWaitInfoVisible - wrapMode: Text.Wrap - } - - BlueButtonType { - id: new_sever_connect - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: new_server_ssh_key.bottom - anchors.topMargin: 10 - - text: StartPageLogic.pushButtonConnectText - onClicked: { - StartPageLogic.onPushButtonConnect() - } - enabled: StartPageLogic.pushButtonConnectEnabled - } - - UrlButtonType { - id: new_sever_connect_key - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: new_sever_connect.bottom - - width: 281 - height: 21 - text: qsTr("Connect using SSH key") - - label.font.pixelSize: 16 - checkable: true - checked: StartPageLogic.pushButtonConnectKeyChecked - onCheckedChanged: { - StartPageLogic.pushButtonConnectKeyChecked = checked - label_new_server_password.text = checked ? qsTr("Private key") : qsTr("Password") - new_sever_connect_key.text = checked ? qsTr("Connect using SSH password") : qsTr("Connect using SSH key") - new_server_password.visible = !checked - new_server_ssh_key.visible = checked - } - } - } - - PopupWithTextField { - id: popupWithTextField - placeholderText: "Enter private key passphrase" - yesFunc: function() { - editingFinished() - close() - StartPageLogic.passphraseDialogClosed() - text = "" - } - noFunc: function() { - close() - StartPageLogic.passphraseDialogClosed() - } - onEditingFinished: { - StartPageLogic.privateKeyPassphrase = text - } - } -} diff --git a/client/ui/qml/Pages/PageVPN.qml b/client/ui/qml/Pages/PageVPN.qml deleted file mode 100644 index 0e6f5078..00000000 --- a/client/ui/qml/Pages/PageVPN.qml +++ /dev/null @@ -1,387 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.Vpn - logic: VpnLogic - - Image { - id: bg_top - anchors.horizontalCenter: parent.horizontalCenter - y: 0 - width: parent.width - height: parent.height * 0.28 - source: "qrc:/images/background_connected.png" - } - - LabelType { - x: 10 - y: 10 - width: 100 - height: 21 - text: VpnLogic.labelVersionText - color: "#dddddd" - font.pixelSize: 12 - } - - UrlButtonType { - id: button_donate - y: 10 - anchors.horizontalCenter: parent.horizontalCenter - height: 21 - label.color: "#D4D4D4" - label.text: qsTr("Donate") - - onClicked: { - UiLogic.goToPage(PageEnum.Test) - } - } - - ImageButtonType { - x: parent.width - 40 - y: 0 - width: 41 - height: 41 - imgMarginHover: 8 - imgMargin: 9 - icon.source: "qrc:/images/settings_grey.png" - onClicked: { - UiLogic.goToPage(PageEnum.GeneralSettings) - } - } - - LabelType { - id: lb_log_enabled - anchors.top: button_donate.bottom - anchors.horizontalCenter: parent.horizontalCenter - width: parent.width - height: 21 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - - text: "Logging enabled!" - color: "#D4D4D4" - - visible: VpnLogic.labelLogEnabledVisible - } - - AnimatedImage { - id: connect_anim - source: "qrc:/images/animation.gif" - anchors.top: bg_top.bottom - anchors.topMargin: 10 - anchors.horizontalCenter: root.horizontalCenter - width: Math.min(parent.width, parent.height) / 4 - height: width - - visible: !VpnLogic.pushButtonConnectVisible - paused: VpnLogic.pushButtonConnectVisible && !root.pageActive - //VisibleBehavior on visible { } - } - - BasicButtonType { - id: button_connect - anchors.horizontalCenter: connect_anim.horizontalCenter - anchors.verticalCenter: connect_anim.verticalCenter - width: connect_anim.width - height: width - checkable: true - checked: VpnLogic.pushButtonConnectChecked - onClicked: VpnLogic.onPushButtonConnectClicked() - background: Image { - anchors.fill: parent - source: button_connect.checked ? "qrc:/images/connected.png" - : "qrc:/images/disconnected.png" - } - contentItem: Item {} - antialiasing: true - enabled: VpnLogic.pushButtonConnectEnabled && VpnLogic.isContainerSupportedByCurrentPlatform - opacity: VpnLogic.pushButtonConnectVisible ? 1 : 0 - -// transitions: Transition { -// NumberAnimation { properties: "opacity"; easing.type: Easing.InOutQuad; duration: 500 } -// } - } - - - LabelType { - id: lb_state - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: button_connect.bottom - width: parent.width - height: 21 - horizontalAlignment: Text.AlignHCenter - text: VpnLogic.labelStateText - } - - RowLayout { - id: layout1 - anchors.top: lb_state.bottom - //anchors.topMargin: 5 - anchors.horizontalCenter: parent.horizontalCenter - height: 21 - - - LabelType { - Layout.alignment: Qt.AlignRight - height: 21 - text: ( VpnLogic.isContainerHaveAuthData ? qsTr("Server") : qsTr("Profile")) + ": " - } - - BasicButtonType { - Layout.alignment: Qt.AlignLeft - height: 21 - background: Item {} - text: VpnLogic.labelCurrentServer + (VpnLogic.isContainerHaveAuthData ? " →" : "") - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - onClicked: { - UiLogic.goToPage(PageEnum.ServersList) - } - } - } - - RowLayout { - id: layout2 - anchors.top: layout1.bottom - anchors.topMargin: 5 - anchors.horizontalCenter: parent.horizontalCenter - height: 21 - - - LabelType { - Layout.alignment: Qt.AlignRight - height: 21 - text: qsTr("Proto") + ": " - } - - BasicButtonType { - Layout.alignment: Qt.AlignLeft - height: 21 - background: Item {} - text: VpnLogic.labelCurrentService + (VpnLogic.isContainerHaveAuthData ? " →" : "") - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - onClicked: { - if (VpnLogic.isContainerHaveAuthData) UiLogic.onGotoCurrentProtocolsPage() - } - } - } - - RowLayout { - id: layout3 - anchors.top: layout2.bottom - anchors.topMargin: 5 - anchors.horizontalCenter: parent.horizontalCenter - height: 21 - - - LabelType { - Layout.alignment: Qt.AlignRight - height: 21 - text: qsTr("DNS") + ": " - } - - BasicButtonType { - Layout.alignment: Qt.AlignLeft - height: 21 - implicitWidth: implicitContentWidth > root.width * 0.6 ? root.width * 0.6 : implicitContentWidth + leftPadding + rightPadding - background: Item {} - text: VpnLogic.labelCurrentDns + (VpnLogic.isContainerHaveAuthData ? " →" : "") - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - onClicked: { - if (VpnLogic.isContainerHaveAuthData) UiLogic.goToPage(PageEnum.NetworkSettings) - } - } - - SvgImageType { - svg.source: VpnLogic.amneziaDnsEnabled ? "qrc:/images/svg/gpp_good_black_24dp.svg" : "qrc:/images/svg/gpp_maybe_black_24dp.svg" - color: VpnLogic.amneziaDnsEnabled ? "#22aa33" : "orange" - width: 25 - height: 25 - } - } - - - LabelType { - id: error_text - anchors.top: layout3.bottom - anchors.horizontalCenter: parent.horizontalCenter - anchors.topMargin: 20 - width: parent.width - 20 - - height: 21 - horizontalAlignment: Text.AlignHCenter - wrapMode: Text.Wrap - text: VpnLogic.labelErrorText - color: "red" - } - - Item { - x: 0 - anchors.bottom: line.top - anchors.bottomMargin: GC.isMobile() ? 0 :10 - width: parent.width - height: 51 - Image { - anchors.horizontalCenter: upload_label.horizontalCenter - y: 10 - width: 15 - height: 15 - source: "qrc:/images/upload.png" - } - Image { - anchors.horizontalCenter: download_label.horizontalCenter - y: 10 - width: 15 - height: 15 - source: "qrc:/images/download.png" - } - Text { - id: download_label - x: 0 - y: 20 - width: 130 - height: 30 - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#4171D6" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.Wrap - text: VpnLogic.labelSpeedReceivedText - } - Text { - id: upload_label - x: parent.width - width - y: 20 - width: 130 - height: 30 - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#42D185" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.Wrap - text: VpnLogic.labelSpeedSentText - } - } - - Rectangle { - id: line - x: 20 - width: parent.width - 40 - height: 1 - anchors.bottom: GC.isMobile() ? root.bottom : conn_type_label.top - anchors.bottomMargin: 10 - color: "#DDDDDD" - } - - Text { - id: conn_type_label - visible: !GC.isMobile() - x: 20 - anchors.bottom: conn_type_group.top - anchors.bottomMargin: GC.isMobile() ? 0 :10 - width: 281 - height: GC.isMobile() ? 0: 21 - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 15 - color: "#181922" - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - wrapMode: Text.Wrap - text: qsTr("How to use VPN") - } - - Item { - id: conn_type_group - x: 20 - visible: !GC.isMobile() - anchors.bottom: button_add_site.top - width: 351 - height: GC.isMobile() ? 0: 91 - enabled: VpnLogic.widgetVpnModeEnabled - RadioButtonType { - x: 0 - y: 0 - width: 341 - height: 19 - checked: VpnLogic.radioButtonVpnModeAllSitesChecked - text: qsTr("For all connections") - onClicked: VpnLogic.onRadioButtonVpnModeAllSitesClicked(true) - } - RadioButtonType { - enabled: VpnLogic.isCustomRoutesSupported - x: 0 - y: 60 - width: 341 - height: 19 - text: qsTr("Except selected sites") - checked: VpnLogic.radioButtonVpnModeExceptSitesChecked - onClicked: VpnLogic.onRadioButtonVpnModeExceptSitesClicked(true) - } - RadioButtonType { - enabled: VpnLogic.isCustomRoutesSupported - x: 0 - y: 30 - width: 341 - height: 19 - text: qsTr("For selected sites") - checked: VpnLogic.radioButtonVpnModeForwardSitesChecked - onClicked: VpnLogic.onRadioButtonVpnModeForwardSitesClicked(true) - } - } - - BasicButtonType { - id: button_add_site - visible: !GC.isMobile() - anchors.horizontalCenter: parent.horizontalCenter - y: parent.height - 60 - width: parent.width - 40 - height: GC.isMobile() ? 0: 40 - text: qsTr("+ Add site") - enabled: ! VpnLogic.radioButtonVpnModeAllSitesChecked - background: Rectangle { - anchors.fill: parent - radius: 4 - color: { - if (!button_add_site.enabled) { - return "#484952" - } - if (button_add_site.containsMouse) { - return "#282932" - } - return "#181922" - } - } - - contentItem: Text { - anchors.fill: parent - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#D4D4D4" - text: button_add_site.text - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - antialiasing: true - onClicked: { - UiLogic.goToPage(PageEnum.Sites) - } - } -} diff --git a/client/ui/qml/Pages/PageViewConfig.qml b/client/ui/qml/Pages/PageViewConfig.qml deleted file mode 100644 index 24ccda45..00000000 --- a/client/ui/qml/Pages/PageViewConfig.qml +++ /dev/null @@ -1,138 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import PageEnum 1.0 -import "./" -import "../Controls" -import "../Config" - -PageBase { - id: root - page: PageEnum.ViewConfig - logic: ViewConfigLogic - - readonly property double rowHeight: ta_last_config.contentHeight / ta_last_config.textArea.lineCount - - BackButton {} - - Caption { - id: caption - text: qsTr("Check config") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - TextAreaType { - id: ta_config - - Layout.topMargin: 5 - Layout.bottomMargin: 20 - Layout.fillWidth: true - Layout.leftMargin: 1 - Layout.rightMargin: 1 - Layout.preferredHeight: ViewConfigLogic.warningActive ? 250 : fl.height - 70 - flickableDirection: Flickable.AutoFlickIfNeeded - - textArea.readOnly: true - textArea.text: logic.configText - } - - LabelType { - id: lb_att - visible: ViewConfigLogic.warningActive - text: qsTr("Attention! -The config above contains cached OpenVPN connection profile. -AmneziaVPN detected this profile may contain malicious scripts. Please, carefully review the config and import this config only if you completely trust it.") - Layout.fillWidth: true - } - - LabelType { - visible: ViewConfigLogic.warningActive - text: qsTr("Suspicious string:") - Layout.fillWidth: true - } - - TextAreaType { - id: ta_mal - visible: ViewConfigLogic.warningActive - - Layout.topMargin: 5 - Layout.bottomMargin: 20 - Layout.fillWidth: true - Layout.leftMargin: 1 - Layout.rightMargin: 1 - Layout.preferredHeight: 60 - flickableDirection: Flickable.AutoFlickIfNeeded - - textArea.readOnly: true - textArea.text: logic.openVpnMalStrings - textArea.textFormat: TextEdit.RichText - } - - LabelType { - visible: ViewConfigLogic.warningActive - text: qsTr("Cached connection profile:") - Layout.fillWidth: true - } - - TextAreaType { - id: ta_last_config - visible: ViewConfigLogic.warningActive - - Layout.topMargin: 5 - Layout.bottomMargin: 20 - Layout.fillWidth: true - Layout.leftMargin: 1 - Layout.rightMargin: 1 - Layout.preferredHeight: 350 - flickableDirection: Flickable.AutoFlickIfNeeded - - textArea.readOnly: true - textArea.text: logic.openVpnLastConfigs - textArea.textFormat: TextEdit.RichText - - Connections { - target: logic - function onWarningStringNumberChanged(n) { - ta_last_config.contentY = rowHeight * n - ta_last_config.height / 2 - } - } - } - - RowLayout { - id: btns_row - - BasicButtonType { - Layout.preferredWidth: (content.width - parent.spacing) /2 - Layout.preferredHeight: 40 - font.pixelSize: btn_import.font.pixelSize - text: qsTr("Cancel") - onClicked: { - UiLogic.closePage() - } - } - - BlueButtonType { - id: btn_import - Layout.preferredWidth: (content.width - parent.spacing) /2 - text: qsTr("Import config") - onClicked: { - logic.importConfig() - } - } - } - } - } - - } diff --git a/client/ui/qml/Pages/Protocols/PageProtoCloak.qml b/client/ui/qml/Pages/Protocols/PageProtoCloak.qml deleted file mode 100644 index 2b5b12ca..00000000 --- a/client/ui/qml/Pages/Protocols/PageProtoCloak.qml +++ /dev/null @@ -1,194 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageProtocolBase { - id: root - protocol: ProtocolEnum.Cloak - logic: UiLogic.protocolLogic(protocol) - - BackButton { - id: back - enabled: !logic.pushButtonCancelVisible - } - - Caption { - id: caption - text: qsTr("Cloak Settings") - } - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: caption.bottom - anchors.left: root.left - anchors.right: root.right - anchors.bottom: pb_save.top - anchors.margins: 20 - anchors.topMargin: 10 - - - RowLayout { - Layout.fillWidth: true - - LabelType { - height: 31 - text: qsTr("Cipher") - Layout.preferredWidth: 0.3 * root.width - 10 - } - - ComboBoxType { - Layout.fillWidth: true - height: 31 - model: [ - qsTr("chacha20-poly1305"), - qsTr("aes-256-gcm"), - qsTr("aes-192-gcm"), - qsTr("aes-128-gcm") - ] - currentIndex: { - for (let i = 0; i < model.length; ++i) { - if (logic.comboBoxCipherText === model[i]) { - return i - } - } - return -1 - } - onCurrentTextChanged: { - logic.comboBoxCipherText = currentText - } - } - } - - RowLayout { - Layout.fillWidth: true - - LabelType { - Layout.preferredWidth: 0.3 * root.width - 10 - height: 31 - text: qsTr("Fake Web Site") - } - - TextFieldType { - id: lineEdit_proto_cloak_site - Layout.fillWidth: true - height: 31 - text: logic.lineEditSiteText - onEditingFinished: { - logic.lineEditSiteText = text - } - } - } - - RowLayout { - Layout.fillWidth: true - - LabelType { - Layout.preferredWidth: 0.3 * root.width - 10 - height: 31 - text: qsTr("Port") - } - - TextFieldType { - id: lineEdit_proto_cloak_port - Layout.fillWidth: true - height: 31 - text: logic.lineEditPortText - onEditingFinished: { - logic.lineEditPortText = text - } - enabled: logic.lineEditPortEnabled - } - } - - Item { - Layout.fillHeight: true - } - } - - LabelType { - id: label_server_busy - horizontalAlignment: Text.AlignHCenter - Layout.maximumWidth: parent.width - Layout.fillWidth: true - visible: logic.labelServerBusyVisible - text: logic.labelServerBusyText - } - - LabelType { - id: label_proto_cloak_info - horizontalAlignment: Text.AlignHCenter - Layout.maximumWidth: parent.width - Layout.fillWidth: true - visible: logic.labelInfoVisible - text: logic.labelInfoText - } - - ProgressBar { - id: progressBar_proto_cloak_reset - anchors.horizontalCenter: parent.horizontalCenter - anchors.fill: pb_save - from: 0 - to: logic.progressBarResetMaximum - value: logic.progressBarResetValue - background: Rectangle { - implicitWidth: parent.width - implicitHeight: parent.height - color: "#100A44" - radius: 4 - } - - contentItem: Item { - implicitWidth: parent.width - implicitHeight: parent.height - Rectangle { - width: progressBar_proto_cloak_reset.visualPosition * parent.width - height: parent.height - radius: 4 - color: Qt.rgba(255, 255, 255, 0.15); - } - } - visible: logic.progressBarResetVisible - LabelType { - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - text: logic.progressBarText - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#D4D4D4" - visible: logic.progressBarTextVisible - } - } - BlueButtonType { - id: pb_save - anchors.horizontalCenter: parent.horizontalCenter - enabled: logic.pageEnabled - anchors.bottom: root.bottom - anchors.bottomMargin: 20 - width: root.width - 60 - height: 40 - text: qsTr("Save and restart VPN") - visible: logic.pushButtonSaveVisible - onClicked: { - logic.onPushButtonSaveClicked() - } - } - - BlueButtonType { - anchors.fill: pb_save - text: qsTr("Cancel") - visible: logic.pushButtonCancelVisible - enabled: logic.pushButtonCancelVisible - onClicked: { - logic.onPushButtonCancelClicked() - } - } -} diff --git a/client/ui/qml/Pages/Protocols/PageProtoOpenVPN.qml b/client/ui/qml/Pages/Protocols/PageProtoOpenVPN.qml deleted file mode 100644 index 507f5f09..00000000 --- a/client/ui/qml/Pages/Protocols/PageProtoOpenVPN.qml +++ /dev/null @@ -1,454 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageProtocolBase { - id: root - protocol: ProtocolEnum.OpenVpn - logic: UiLogic.protocolLogic(protocol) - - BackButton { - id: back - enabled: !logic.pushButtonCancelVisible - } - - Caption { - id: caption - text: qsTr("OpenVPN Settings") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - - ColumnLayout { - id: content - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: GC.defaultMargin - 1 - - ColumnLayout { - visible: !logic.isThirdPartyConfig - - LabelType { - id: lb_subnet - enabled: logic.pageEnabled - height: 21 - text: qsTr("VPN Addresses Subnet") - } - - TextFieldType { - id: tf_subnet - enabled: logic.pageEnabled - implicitWidth: parent.width - height: 31 - text: logic.lineEditSubnetText - onEditingFinished: { - logic.lineEditSubnetText = text - } - } - - LabelType { - id: lb_proto - enabled: logic.pageEnabled - Layout.topMargin: 20 - height: 21 - text: qsTr("Network protocol") - } - - Rectangle { - id: rect_proto - enabled: logic.pageEnabled - implicitWidth: parent.width - height: 71 - border.width: 1 - border.color: "lightgray" - radius: 2 - RadioButtonType { - x: 10 - y: 40 - width: 171 - height: 19 - text: qsTr("TCP") - enabled: logic.radioButtonTcpEnabled - checked: logic.radioButtonTcpChecked - onCheckedChanged: { - logic.radioButtonTcpChecked = checked - } - } - RadioButtonType { - x: 10 - y: 10 - width: 171 - height: 19 - text: qsTr("UDP") - checked: logic.radioButtonUdpChecked - onCheckedChanged: { - logic.radioButtonUdpChecked = checked - } - enabled: logic.radioButtonUdpEnabled - } - } - - RowLayout { - enabled: logic.pageEnabled - Layout.topMargin: 10 - Layout.fillWidth: true - LabelType { - id: lb_port - height: 31 - text: qsTr("Port") - Layout.preferredWidth: root.width / 2 - 10 - } - TextFieldType { - id: tf_port - Layout.fillWidth: true - - height: 31 - text: logic.lineEditPortText - onEditingFinished: { - logic.lineEditPortText = text - } - enabled: logic.lineEditPortEnabled - } - } - - CheckBoxType { - id: check_auto_enc - enabled: logic.pageEnabled - implicitWidth: parent.width - height: 21 - text: qsTr("Auto-negotiate encryption") - checked: logic.checkBoxAutoEncryptionChecked - onCheckedChanged: { - logic.checkBoxAutoEncryptionChecked = checked - } - onClicked: { - logic.checkBoxAutoEncryptionClicked() - } - } - - LabelType { - id: lb_cipher - enabled: logic.pageEnabled - height: 21 - text: qsTr("Cipher") - } - - ComboBoxType { - id: cb_cipher - enabled: logic.pageEnabled && !check_auto_enc.checked - implicitWidth: parent.width - - height: 31 - model: [ - qsTr("AES-256-GCM"), - qsTr("AES-192-GCM"), - qsTr("AES-128-GCM"), - qsTr("AES-256-CBC"), - qsTr("AES-192-CBC"), - qsTr("AES-128-CBC"), - qsTr("ChaCha20-Poly1305"), - qsTr("ARIA-256-CBC"), - qsTr("CAMELLIA-256-CBC"), - qsTr("none") - ] - currentIndex: { - for (let i = 0; i < model.length; ++i) { - if (logic.comboBoxVpnCipherText === model[i]) { - return i - } - } - return -1 - } - onCurrentTextChanged: { - logic.comboBoxVpnCipherText = currentText - } - } - - LabelType { - id: lb_hash - enabled: logic.pageEnabled - height: 21 - Layout.topMargin: 20 - text: qsTr("Hash") - } - - ComboBoxType { - id: cb_hash - enabled: logic.pageEnabled && !check_auto_enc.checked - height: 31 - implicitWidth: parent.width - model: [ - qsTr("SHA512"), - qsTr("SHA384"), - qsTr("SHA256"), - qsTr("SHA3-512"), - qsTr("SHA3-384"), - qsTr("SHA3-256"), - qsTr("whirlpool"), - qsTr("BLAKE2b512"), - qsTr("BLAKE2s256"), - qsTr("SHA1") - ] - currentIndex: { - for (let i = 0; i < model.length; ++i) { - if (logic.comboBoxVpnHashText === model[i]) { - return i - } - } - return -1 - } - onCurrentTextChanged: { - logic.comboBoxVpnHashText = currentText - } - } - - CheckBoxType { - id: check_tls - enabled: logic.pageEnabled - implicitWidth: parent.width - Layout.topMargin: 20 - height: 21 - text: qsTr("Enable TLS auth") - checked: logic.checkBoxTlsAuthChecked - onCheckedChanged: { - logic.checkBoxTlsAuthChecked = checked - } - - } - - CheckBoxType { - id: check_block_dns - enabled: logic.pageEnabled - implicitWidth: parent.width - height: 21 - text: qsTr("Block DNS requests outside of VPN") - checked: logic.checkBoxBlockDnsChecked - onCheckedChanged: { - logic.checkBoxBlockDnsChecked = checked - } - } - - BasicButtonType { - id: pb_client_config - enabled: logic.pageEnabled - implicitWidth: parent.width - height: 21 - text: qsTr("Additional client config commands →") - background: Item { - anchors.fill: parent - } - - contentItem: Text { - anchors.fill: parent - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#15CDCB"; - text: pb_client_config.text - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - } - antialiasing: true - checkable: true - checked: StartPageLogic.pushButtonConnectKeyChecked - } - - Rectangle { - id: rect_client_conf - enabled: logic.pageEnabled - implicitWidth: root.width - 60 - height: 101 - border.width: 1 - border.color: "lightgray" - radius: 2 - visible: pb_client_config.checked - - ScrollView { - anchors.fill: parent - TextArea { - id: te_client_config - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#181922" - text: logic.textAreaAdditionalClientConfig - onEditingFinished: { - logic.textAreaAdditionalClientConfig = text - } - } - } - } - - BasicButtonType { - id: pb_server_config - enabled: logic.pageEnabled - implicitWidth: parent.width - height: 21 - text: qsTr("Additional server config commands →") - background: Item { - anchors.fill: parent - } - - contentItem: Text { - anchors.fill: parent - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#15CDCB"; - text: pb_server_config.text - horizontalAlignment: Text.AlignLeft - verticalAlignment: Text.AlignVCenter - } - antialiasing: true - checkable: true - checked: StartPageLogic.pushButtonConnectKeyChecked - } - - Rectangle { - id: rect_server_conf - enabled: logic.pageEnabled - implicitWidth: root.width - 60 - height: 101 - border.width: 1 - border.color: "lightgray" - radius: 2 - visible: pb_server_config.checked - - ScrollView { - anchors.fill: parent - TextArea { - id: te_server_config - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#181922" - text: logic.textAreaAdditionalServerConfig - onEditingFinished: { - logic.textAreaAdditionalServerConfig = text - } - } - } - } - - LabelType { - id: label_server_busy - enabled: logic.pageEnabled - horizontalAlignment: Text.AlignHCenter - Layout.maximumWidth: parent.width - Layout.fillWidth: true - visible: logic.labelServerBusyVisible - text: logic.labelServerBusyText - } - - LabelType { - id: label_proto_openvpn_info - enabled: logic.pageEnabled - horizontalAlignment: Text.AlignHCenter - Layout.maximumWidth: parent.width - Layout.fillWidth: true - height: 41 - visible: logic.labelProtoOpenVpnInfoVisible - text: logic.labelProtoOpenVpnInfoText - } - - Rectangle { - id: it_save - implicitWidth: parent.width - Layout.topMargin: 20 - height: 40 - - BlueButtonType { - id: pb_save - enabled: logic.pageEnabled - z: 1 - height: 40 - text: qsTr("Save and restart VPN") - width: parent.width - visible: logic.pushButtonSaveVisible - onClicked: { - logic.onPushButtonSaveClicked() - } - } - - BlueButtonType { - z: 1 - anchors.fill: pb_save - text: qsTr("Cancel") - visible: logic.pushButtonCancelVisible - enabled: logic.pushButtonCancelVisible - onClicked: { - logic.onPushButtonCancelClicked() - } - } - - ProgressBar { - id: progress_save - anchors.fill: pb_save - from: 0 - to: logic.progressBarResetMaximum - value: logic.progressBarResetValue - visible: logic.progressBarResetVisible - background: Rectangle { - implicitWidth: parent.width - implicitHeight: parent.height - color: "#100A44" - radius: 4 - } - - contentItem: Item { - implicitWidth: parent.width - implicitHeight: parent.height - Rectangle { - width: progress_save.visualPosition * parent.width - height: parent.height - radius: 4 - color: Qt.rgba(255, 255, 255, 0.15); - } - } - } - - LabelType { - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - text: logic.progressBarText - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#D4D4D4" - visible: logic.progressBarTextVisible - } - } - - } - - ColumnLayout { - visible: logic.isThirdPartyConfig - TextAreaType { - id: ta_config - - Layout.topMargin: 5 - Layout.bottomMargin: 20 - Layout.fillWidth: true - Layout.leftMargin: 1 - Layout.rightMargin: 1 - Layout.preferredHeight: fl.height - 70 - flickableDirection: Flickable.AutoFlickIfNeeded - - textArea.readOnly: true - textArea.text: logic.openVpnLastConfigText - } - } - } - } -} diff --git a/client/ui/qml/Pages/Protocols/PageProtoSftp.qml b/client/ui/qml/Pages/Protocols/PageProtoSftp.qml deleted file mode 100644 index c6a0602f..00000000 --- a/client/ui/qml/Pages/Protocols/PageProtoSftp.qml +++ /dev/null @@ -1,140 +0,0 @@ -import QtQuick -import QtQuick.Controls -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageProtocolBase { - id: root - protocol: ProtocolEnum.Sftp - logic: UiLogic.protocolLogic(protocol) - - BackButton { - id: back - } - - Caption { - id: caption - text: qsTr("SFTP settings") - } - - Rectangle { - id: frame_settings - width: parent.width - anchors.top: caption.bottom - anchors.topMargin: 10 - - border.width: 1 - border.color: "lightgray" - anchors.bottomMargin: 5 - anchors.horizontalCenter: parent.horizontalCenter - radius: 2 - Grid { - id: grid - anchors.fill: parent - columns: 2 - horizontalItemAlignment: Grid.AlignHCenter - verticalItemAlignment: Grid.AlignVCenter - topPadding: 5 - leftPadding: 30 - rightPadding: 30 - spacing: 5 - - - LabelType { - width: 130 - text: qsTr("Port") - } - TextFieldType { - id: tf_port_num - width: parent.width - 130 - parent.spacing - parent.leftPadding * 2 - text: logic.labelTftpPortText - readOnly: true - } - - LabelType { - width: 130 - text: qsTr("User Name") - } - TextFieldType { - id: tf_user_name - width: parent.width - 130 - parent.spacing - parent.leftPadding * 2 - text: logic.labelTftpUserNameText - readOnly: true - } - - LabelType { - width: 130 - text: qsTr("Password") - } - TextFieldType { - id: tf_password - width: parent.width - 130 - parent.spacing - parent.leftPadding * 2 - text: logic.labelTftpPasswordText - readOnly: true - } - } - } - - RichLabelType { - anchors.bottom: check_persist.top - anchors.bottomMargin: 10 - width: parent.width - 60 - x: 30 - font.pixelSize: 14 - - readonly property string windows_text: "In order to mount remote SFTP folder as local drive, perform following steps: -
    -
  • Install the latest version of WinFsp.
  • -
  • Install the latest version of SSHFS-Win. Choose the x64 or x86 installer according to your computer's architecture.
  • -
" - - readonly property string macos_text: "In order to mount remote SFTP folder as local folder, perform following steps: -
    -
  • Install the latest version of macFUSE.
  • -
  • Install the latest version of SSHFS.
  • -
" - - text: { - if (Qt.platform.os == "windows") return windows_text - else if (Qt.platform.os == "osx") return macos_text - else if (Qt.platform.os == "linux") return "" - else return "" - } - } - - CheckBoxType { - id: check_persist - visible: false - anchors.bottom: pb_mount.top - anchors.bottomMargin: 10 - x: 30 - width: parent.width - height: 21 - text: qsTr("Restore drive when client starts") - checked: logic.checkBoxSftpRestoreChecked - onCheckedChanged: { - logic.checkBoxSftpRestoreChecked = checked - } - onClicked: { - logic.checkBoxSftpRestoreClicked() - } - } - - BlueButtonType { - id: pb_mount - visible: GC.isDesktop() - enabled: logic.pushButtonSftpMountEnabled - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: parent.bottom - anchors.bottomMargin: 20 - x: 30 - width: parent.width - 60 - height: 40 - text: qsTr("Mount drive") - onClicked: { - logic.onPushButtonSftpMountDriveClicked() - } - } -} diff --git a/client/ui/qml/Pages/Protocols/PageProtoShadowSocks.qml b/client/ui/qml/Pages/Protocols/PageProtoShadowSocks.qml deleted file mode 100644 index f3b1f83d..00000000 --- a/client/ui/qml/Pages/Protocols/PageProtoShadowSocks.qml +++ /dev/null @@ -1,175 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageProtocolBase { - id: root - protocol: ProtocolEnum.ShadowSocks - logic: UiLogic.protocolLogic(protocol) - - BackButton { - id: back - enabled: !logic.pushButtonCancelVisible - } - - Caption { - id: caption - text: qsTr("ShadowSocks Settings") - } - - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: caption.bottom - anchors.left: root.left - anchors.right: root.right - anchors.bottom: pb_save.top - anchors.margins: 20 - anchors.topMargin: 10 - - - - RowLayout { - Layout.fillWidth: true - - LabelType { - height: 31 - text: qsTr("Cipher") - Layout.preferredWidth: 0.3 * root.width - 10 - } - - ComboBoxType { - height: 31 - Layout.fillWidth: true - - model: [ - qsTr("chacha20-ietf-poly1305"), - qsTr("xchacha20-ietf-poly1305"), - qsTr("aes-256-gcm"), - qsTr("aes-192-gcm"), - qsTr("aes-128-gcm") - ] - currentIndex: { - for (let i = 0; i < model.length; ++i) { - if (logic.comboBoxCipherText === model[i]) { - return i - } - } - return -1 - } - } - } - - RowLayout { - Layout.fillWidth: true - - LabelType { - Layout.preferredWidth: 0.3 * root.width - 10 - height: 31 - text: qsTr("Port") - } - - TextFieldType { - id: lineEdit_proto_shadowsocks_port - Layout.fillWidth: true - height: 31 - text: logic.lineEditPortText - onEditingFinished: { - logic.lineEditPortText = text - } - enabled: logic.lineEditPortEnabled - } - } - - Item { - Layout.fillHeight: true - } - - LabelType { - id: label_server_busy - horizontalAlignment: Text.AlignHCenter - Layout.maximumWidth: parent.width - Layout.fillWidth: true - visible: logic.labelServerBusyVisible - text: logic.labelServerBusyText - } - - LabelType { - id: label_proto_shadowsocks_info - horizontalAlignment: Text.AlignHCenter - Layout.maximumWidth: parent.width - Layout.fillWidth: true - visible: logic.labelInfoVisible - text: logic.labelInfoText - } - } - - ProgressBar { - id: progressBar_reset - anchors.fill: pb_save - from: 0 - to: logic.progressBarResetMaximum - value: logic.progressBarResetValue - visible: logic.progressBarResetVisible - background: Rectangle { - implicitWidth: parent.width - implicitHeight: parent.height - color: "#100A44" - radius: 4 - } - - contentItem: Item { - implicitWidth: parent.width - implicitHeight: parent.height - Rectangle { - width: progressBar_reset.visualPosition * parent.width - height: parent.height - radius: 4 - color: Qt.rgba(255, 255, 255, 0.15); - } - } - LabelType { - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - text: logic.progressBarText - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#D4D4D4" - visible: logic.progressBarTextVisible - } - } - - BlueButtonType { - id: pb_save - enabled: logic.pageEnabled - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: root.bottom - anchors.bottomMargin: 20 - width: root.width - 60 - height: 40 - text: qsTr("Save and restart VPN") - visible: logic.pushButtonSaveVisible - onClicked: { - logic.onPushButtonSaveClicked() - } - } - - BlueButtonType { - anchors.fill: pb_save - text: qsTr("Cancel") - visible: logic.pushButtonCancelVisible - enabled: logic.pushButtonCancelVisible - onClicked: { - logic.onPushButtonCancelClicked() - } - } -} diff --git a/client/ui/qml/Pages/Protocols/PageProtoTorWebSite.qml b/client/ui/qml/Pages/Protocols/PageProtoTorWebSite.qml deleted file mode 100644 index aa4d35d4..00000000 --- a/client/ui/qml/Pages/Protocols/PageProtoTorWebSite.qml +++ /dev/null @@ -1,69 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageProtocolBase { - id: root - protocol: ProtocolEnum.TorWebSite - logic: UiLogic.protocolLogic(protocol) - - BackButton { - id: back - } - - Caption { - id: caption - text: qsTr("Tor Web Site settings") - } - - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: caption.bottom - anchors.left: root.left - anchors.right: root.right - anchors.margins: 20 - anchors.topMargin: 10 - - - - RowLayout { - Layout.fillWidth: true - - LabelType { - id: lbl_onion - Layout.preferredWidth: 0.3 * root.width - 10 - text: qsTr("Web site onion address") - } - TextFieldType { - id: tf_site_address - Layout.fillWidth: true - text: logic.labelTorWebSiteAddressText - readOnly: true - } - } - - ShareConnectionButtonCopyType { - Layout.fillWidth: true - Layout.topMargin: 5 - copyText: tf_site_address.text - } - - RichLabelType { - Layout.fillWidth: true - Layout.topMargin: 15 - text: qsTr("Notes:
    -
  • Use Tor Browser to open this url.
  • -
  • After installation it takes several minutes while your onion site will become available in the Tor Network.
  • -
  • When configuring WordPress set the domain as this onion address.
  • -
-") - } - } - -} diff --git a/client/ui/qml/Pages/Protocols/PageProtoWireGuard.qml b/client/ui/qml/Pages/Protocols/PageProtoWireGuard.qml deleted file mode 100644 index e4dde50e..00000000 --- a/client/ui/qml/Pages/Protocols/PageProtoWireGuard.qml +++ /dev/null @@ -1,60 +0,0 @@ -import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.15 -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageProtocolBase { - id: root - protocol: ProtocolEnum.WireGuard - logic: UiLogic.protocolLogic(protocol) - - BackButton { - id: back - } - Caption { - id: caption - text: qsTr("WireGuard Settings") - } - - Flickable { - id: fl - width: root.width - anchors.top: caption.bottom - anchors.topMargin: 20 - anchors.bottom: parent.bottom - anchors.bottomMargin: 20 - anchors.left: root.left - anchors.leftMargin: 30 - anchors.right: root.right - anchors.rightMargin: 30 - - contentHeight: content.height - clip: true - - ColumnLayout { - id: content - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - - TextAreaType { - id: ta_config - - Layout.topMargin: 5 - Layout.bottomMargin: 20 - Layout.fillWidth: true - Layout.leftMargin: 1 - Layout.rightMargin: 1 - Layout.preferredHeight: fl.height - 70 - flickableDirection: Flickable.AutoFlickIfNeeded - - textArea.readOnly: true - textArea.text: logic.wireGuardLastConfigText - } - } - } - -} diff --git a/client/ui/qml/Pages/Protocols/PageProtocolBase.qml b/client/ui/qml/Pages/Protocols/PageProtocolBase.qml deleted file mode 100644 index 97a0f1eb..00000000 --- a/client/ui/qml/Pages/Protocols/PageProtocolBase.qml +++ /dev/null @@ -1,13 +0,0 @@ -import QtQuick -import QtQuick.Controls -import PageEnum 1.0 -import ProtocolEnum 1.0 -import "./.." -import "../../Controls" -import "../../Config" - -PageBase { - id: root - property var protocol: ProtocolEnum.Any - page: PageEnum.ProtocolSettings -} diff --git a/client/ui/qml/Pages/Share/PageShareProtoAmnezia.qml b/client/ui/qml/Pages/Share/PageShareProtoAmnezia.qml deleted file mode 100644 index 4e52e501..00000000 --- a/client/ui/qml/Pages/Share/PageShareProtoAmnezia.qml +++ /dev/null @@ -1,145 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageShareProtocolBase { - id: root - protocol: ProtocolEnum.Any - - BackButton { - id: back - } - Caption { - id: caption - text: qsTr("Share for Amnezia") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height + 20 - - Behavior on contentY{ - NumberAnimation { - duration: 300 - easing.type: Easing.InOutCubic - } - } - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - Text { - id: lb_desc - Layout.fillWidth: true - font.family: "Lato" - font.styleName: "normal" - font.pixelSize: 16 - color: "#181922" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - wrapMode: Text.Wrap - text: ShareConnectionLogic.shareFullAccess - ? qsTr("Anyone who logs in with this code will have the same permissions to use VPN and YOUR SERVER as you. \n -This code includes your server credentials!\n -Provide this code only to TRUSTED users.") - : qsTr("Anyone who logs in with this code will be able to connect to this VPN server. \n -This code does not include server credentials.\n -New encryption keys pair will be generated.") - } - - ShareConnectionButtonType { - Layout.topMargin: 20 - Layout.fillWidth: true - Layout.preferredHeight: 40 - - text: ShareConnectionLogic.shareFullAccess - ? showConfigText - : (genConfigProcess ? generatingConfigText : generateConfigText) - onClicked: { - enabled = false - genConfigProcess = true - ShareConnectionLogic.onPushButtonShareAmneziaGenerateClicked() - enabled = true - genConfigProcess = false - fl.contentY = tfShareCode.mapToItem(fl.contentItem, 0, 0).y - } - } - - TextAreaType { - id: tfShareCode - - Layout.topMargin: 20 - Layout.bottomMargin: 20 - Layout.preferredHeight: 200 - - Layout.fillWidth: true - - textArea.readOnly: true - textArea.wrapMode: TextEdit.WrapAnywhere - textArea.verticalAlignment: Text.AlignTop - textArea.text: ShareConnectionLogic.textEditShareAmneziaCodeText - - visible: tfShareCode.textArea.length > 0 - } - - - ShareConnectionButtonCopyType { - Layout.bottomMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - copyText: tfShareCode.textArea.text - } - ShareConnectionButtonType { - Layout.bottomMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - text: Qt.platform.os === "android" ? qsTr("Share") : qsTr("Save to file") - enabled: tfShareCode.textArea.length > 0 - visible: tfShareCode.textArea.length > 0 - - onClicked: { - UiLogic.saveTextFile(qsTr("Save AmneziaVPN config"), "amnezia_config.vpn", "*.vpn", tfShareCode.textArea.text) - } - } - - Image { - id: image_share_code - Layout.topMargin: 20 - Layout.fillWidth: true - Layout.preferredHeight: width - smooth: false - - Timer { - property int idx: 0 - interval: 1000 - running: root.pageActive && ShareConnectionLogic.shareAmneziaQrCodeTextSeriesLength > 0 - repeat: true - onTriggered: { - idx++ - if (idx >= ShareConnectionLogic.shareAmneziaQrCodeTextSeriesLength) { - idx = 0 - } - image_share_code.source = ShareConnectionLogic.shareAmneziaQrCodeTextSeries[idx] - } - } - - visible: ShareConnectionLogic.shareAmneziaQrCodeTextSeriesLength > 0 - } - - LabelType { - Layout.fillWidth: true - text: qsTr("Scan QR code using AmneziaVPN mobile") - } - } - } -} diff --git a/client/ui/qml/Pages/Share/PageShareProtoCloak.qml b/client/ui/qml/Pages/Share/PageShareProtoCloak.qml deleted file mode 100644 index 92bcb832..00000000 --- a/client/ui/qml/Pages/Share/PageShareProtoCloak.qml +++ /dev/null @@ -1,99 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageShareProtocolBase { - id: root - protocol: ProtocolEnum.Cloak - - BackButton { - id: back - } - Caption { - id: caption - text: qsTr("Share Cloak Settings") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - LabelType { - id: lb_desc - Layout.fillWidth: true - Layout.topMargin: 10 - - horizontalAlignment: Text.AlignHCenter - - wrapMode: Text.Wrap - text: qsTr("Note: Cloak protocol using same password for all connections") - } - - ShareConnectionButtonType { - Layout.topMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - - text: genConfigProcess ? generatingConfigText : generateConfigText - onClicked: { - enabled = false - genConfigProcess = true - ShareConnectionLogic.onPushButtonShareCloakGenerateClicked() - enabled = true - genConfigProcess = false - } - } - - TextAreaType { - id: tfShareCode - - Layout.topMargin: 20 - Layout.bottomMargin: 20 - Layout.fillWidth: true - Layout.preferredHeight: 200 - - textArea.readOnly: true - textArea.text: ShareConnectionLogic.textEditShareCloakText - - visible: tfShareCode.textArea.length > 0 - } - - ShareConnectionButtonCopyType { - Layout.bottomMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - - copyText: tfShareCode.textArea.text - } - - ShareConnectionButtonType { - Layout.bottomMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - - text: Qt.platform.os === "android" ? qsTr("Share") : qsTr("Save to file") - enabled: tfShareCode.textArea.length > 0 - visible: tfShareCode.textArea.length > 0 - - onClicked: { - UiLogic.saveTextFile(qsTr("Save AmneziaVPN config"), "amnezia_config_cloak.json", "*.json", tfShareCode.textArea.text) - } - } - - } - } - -} diff --git a/client/ui/qml/Pages/Share/PageShareProtoIkev2.qml b/client/ui/qml/Pages/Share/PageShareProtoIkev2.qml deleted file mode 100644 index 7e41650a..00000000 --- a/client/ui/qml/Pages/Share/PageShareProtoIkev2.qml +++ /dev/null @@ -1,131 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageShareProtocolBase { - id: root - protocol: ProtocolEnum.Ikev2 - - BackButton { - id: back - } - Caption { - id: caption - text: qsTr("Share IKEv2 Settings") - } - - TextAreaType { - id: tfCert - textArea.readOnly: true - textArea.text: ShareConnectionLogic.textEditShareIkev2CertText - - visible: false - } - - TextAreaType { - id: tfMobileConfig - textArea.readOnly: true - textArea.text: ShareConnectionLogic.textEditShareIkev2MobileConfigText - - visible: false - } - - TextAreaType { - id: tfStrongSwanConfig - textArea.readOnly: true - textArea.text: ShareConnectionLogic.textEditShareIkev2StrongSwanConfigText - - visible: false - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - -// LabelType { -// id: lb_desc -// Layout.fillWidth: true -// Layout.topMargin: 10 - -// horizontalAlignment: Text.AlignHCenter - -// wrapMode: Text.Wrap -// text: qsTr("Note: ShadowSocks protocol using same password for all connections") -// } - - ShareConnectionButtonType { - Layout.topMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - - text: genConfigProcess ? generatingConfigText : generateConfigText - onClicked: { - enabled = false - genConfigProcess = true - ShareConnectionLogic.onPushButtonShareIkev2GenerateClicked() - enabled = true - genConfigProcess = false - } - } - - ShareConnectionButtonType { - Layout.topMargin: 30 - Layout.bottomMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - width: parent.width - 60 - - text: qsTr("Export p12 certificate") - enabled: tfCert.textArea.length > 0 - visible: tfCert.textArea.length > 0 - - onClicked: { - UiLogic.saveTextFile(qsTr("Export p12 certificate"), "amnezia_ikev2_cert_for_windows.p12", "*.p12", tfCert.textArea.text) - } - } - - ShareConnectionButtonType { - Layout.bottomMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - width: parent.width - 60 - - text: qsTr("Export config for Apple") - enabled: tfMobileConfig.textArea.length > 0 - visible: tfMobileConfig.textArea.length > 0 - - onClicked: { - UiLogic.saveTextFile(qsTr("Export config for Apple"), "amnezia_for_apple.plist", "*.plist", tfMobileConfig.textArea.text) - } - } - - ShareConnectionButtonType { - Layout.bottomMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - width: parent.width - 60 - - text: qsTr("Export config for StrongSwan") - enabled: tfStrongSwanConfig.textArea.length > 0 - visible: tfStrongSwanConfig.textArea.length > 0 - - onClicked: { - UiLogic.saveTextFile(qsTr("Export config for StrongSwan"), "amnezia_for_StrongSwan.profile", "*.profile", tfStrongSwanConfig.textArea.text) - } - } - } - } -} diff --git a/client/ui/qml/Pages/Share/PageShareProtoOpenVPN.qml b/client/ui/qml/Pages/Share/PageShareProtoOpenVPN.qml deleted file mode 100644 index 4246be21..00000000 --- a/client/ui/qml/Pages/Share/PageShareProtoOpenVPN.qml +++ /dev/null @@ -1,96 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageShareProtocolBase { - id: root - protocol: ProtocolEnum.OpenVpn - - BackButton { - id: back - } - Caption { - id: caption - text: qsTr("Share OpenVPN Settings") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - LabelType { - id: lb_desc - Layout.fillWidth: true - Layout.topMargin: 10 - - horizontalAlignment: Text.AlignHCenter - - wrapMode: Text.Wrap - text: qsTr("New encryption keys pair will be generated.") - } - - ShareConnectionButtonType { - Layout.topMargin: 20 - Layout.fillWidth: true - Layout.preferredHeight: 40 - - text: genConfigProcess ? generatingConfigText : generateConfigText - onClicked: { - enabled = false - genConfigProcess = true - ShareConnectionLogic.onPushButtonShareOpenVpnGenerateClicked() - genConfigProcess = false - enabled = true - } - } - - TextAreaType { - id: tfShareCode - Layout.topMargin: 20 - Layout.preferredHeight: 200 - Layout.fillWidth: true - - textArea.readOnly: true - - textArea.verticalAlignment: Text.AlignTop - textArea.text: ShareConnectionLogic.textEditShareOpenVpnCodeText - - visible: tfShareCode.textArea.length > 0 - } - - ShareConnectionButtonCopyType { - Layout.preferredHeight: 40 - Layout.fillWidth: true - - copyText: tfShareCode.textArea.text - } - ShareConnectionButtonType { - Layout.bottomMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - width: parent.width - 60 - - text: Qt.platform.os === "android" ? qsTr("Share") : qsTr("Save to file") - enabled: tfShareCode.textArea.length > 0 - visible: tfShareCode.textArea.length > 0 - - onClicked: { - UiLogic.saveTextFile(qsTr("Save OpenVPN config"), "amnezia_for_openvpn.ovpn", "*.ovpn", tfShareCode.textArea.text) - } - } - } - } -} diff --git a/client/ui/qml/Pages/Share/PageShareProtoSftp.qml b/client/ui/qml/Pages/Share/PageShareProtoSftp.qml deleted file mode 100644 index 8f990ac1..00000000 --- a/client/ui/qml/Pages/Share/PageShareProtoSftp.qml +++ /dev/null @@ -1,21 +0,0 @@ -import QtQuick -import QtQuick.Controls -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageShareProtocolBase { - id: root - protocol: ProtocolEnum.Sftp - - BackButton { - id: back - } - - Caption { - id: caption - text: qsTr("Share SFTP settings") - } - - } diff --git a/client/ui/qml/Pages/Share/PageShareProtoShadowSocks.qml b/client/ui/qml/Pages/Share/PageShareProtoShadowSocks.qml deleted file mode 100644 index 91f5e8d2..00000000 --- a/client/ui/qml/Pages/Share/PageShareProtoShadowSocks.qml +++ /dev/null @@ -1,115 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageShareProtocolBase { - id: root - protocol: ProtocolEnum.ShadowSocks - - BackButton { - id: back - } - Caption { - id: caption - text: qsTr("Share ShadowSocks Settings") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - LabelType { - id: lb_desc - Layout.fillWidth: true - Layout.topMargin: 10 - - horizontalAlignment: Text.AlignHCenter - - wrapMode: Text.Wrap - text: qsTr("Note: ShadowSocks protocol using same password for all connections") - } - - ShareConnectionButtonType { - Layout.topMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - - text: genConfigProcess ? generatingConfigText : generateConfigText - onClicked: { - enabled = false - genConfigProcess = true - ShareConnectionLogic.onPushButtonShareShadowSocksGenerateClicked() - enabled = true - genConfigProcess = false - } - } - - TextAreaType { - id: tfShareCode - - Layout.topMargin: 20 - Layout.preferredHeight: 200 - Layout.fillWidth: true - - textArea.readOnly: true - textArea.wrapMode: TextEdit.WrapAnywhere - textArea.verticalAlignment: Text.AlignTop - textArea.text: ShareConnectionLogic.textEditShareShadowSocksText - - visible: tfShareCode.textArea.length > 0 - } - ShareConnectionButtonCopyType { - Layout.preferredHeight: 40 - Layout.fillWidth: true - Layout.bottomMargin: 20 - - start_text: qsTr("Copy config") - copyText: tfShareCode.textArea.text - } - - LabelType { - height: 20 - visible: tfConnString.length > 0 - text: qsTr("Connection string") - } - TextFieldType { - id: tfConnString - height: 100 - horizontalAlignment: Text.AlignHCenter - Layout.fillWidth: true - text: ShareConnectionLogic.lineEditShareShadowSocksStringText - visible: tfConnString.length > 0 - - readOnly: true - } - ShareConnectionButtonCopyType { - Layout.preferredHeight: 40 - Layout.fillWidth: true - start_text: qsTr("Copy string") - copyText: tfConnString.text - } - - Image { - id: label_share_ss_qr_code - Layout.topMargin: 20 - Layout.fillWidth: true - Layout.preferredHeight: width - smooth: false - source: ShareConnectionLogic.shareShadowSocksQrCodeText - } - } - } -} diff --git a/client/ui/qml/Pages/Share/PageShareProtoTorWebSite.qml b/client/ui/qml/Pages/Share/PageShareProtoTorWebSite.qml deleted file mode 100644 index 1c68f6cb..00000000 --- a/client/ui/qml/Pages/Share/PageShareProtoTorWebSite.qml +++ /dev/null @@ -1,20 +0,0 @@ -import QtQuick -import QtQuick.Controls -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageShareProtocolBase { - id: root - protocol: ProtocolEnum.TorWebSite - - BackButton { - id: back - } - - Caption { - id: caption - text: qsTr("Share Tor Web site") - } -} diff --git a/client/ui/qml/Pages/Share/PageShareProtoWireGuard.qml b/client/ui/qml/Pages/Share/PageShareProtoWireGuard.qml deleted file mode 100644 index 7265de81..00000000 --- a/client/ui/qml/Pages/Share/PageShareProtoWireGuard.qml +++ /dev/null @@ -1,103 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import ProtocolEnum 1.0 -import "../" -import "../../Controls" -import "../../Config" - -PageShareProtocolBase { - id: root - protocol: ProtocolEnum.WireGuard - - BackButton { - id: back - } - Caption { - id: caption - text: qsTr("Share WireGuard Settings") - } - - FlickableType { - id: fl - anchors.top: caption.bottom - contentHeight: content.height - - ColumnLayout { - id: content - enabled: logic.pageEnabled - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.rightMargin: 15 - - LabelType { - id: lb_desc - Layout.fillWidth: true - Layout.topMargin: 10 - - horizontalAlignment: Text.AlignHCenter - - wrapMode: Text.Wrap - text: qsTr("New encryption keys pair will be generated.") - } - - ShareConnectionButtonType { - Layout.topMargin: 10 - Layout.fillWidth: true - Layout.preferredHeight: 40 - - text: genConfigProcess ? generatingConfigText : generateConfigText - onClicked: { - enabled = false - genConfigProcess = true - ShareConnectionLogic.onPushButtonShareWireGuardGenerateClicked() - enabled = true - genConfigProcess = false - } - } - - TextAreaType { - id: tfShareCode - - Layout.topMargin: 20 - Layout.preferredHeight: 200 - Layout.fillWidth: true - - textArea.readOnly: true - textArea.wrapMode: TextEdit.WrapAnywhere - textArea.verticalAlignment: Text.AlignTop - textArea.text: ShareConnectionLogic.textEditShareWireGuardCodeText - - visible: tfShareCode.textArea.length > 0 - } - ShareConnectionButtonCopyType { - Layout.preferredHeight: 40 - Layout.fillWidth: true - - copyText: tfShareCode.textArea.text - } - - ShareConnectionButtonType { - Layout.preferredHeight: 40 - Layout.fillWidth: true - - text: Qt.platform.os === "android" ? qsTr("Share") : qsTr("Save to file") - enabled: tfShareCode.textArea.length > 0 - visible: tfShareCode.textArea.length > 0 - - onClicked: { - UiLogic.saveTextFile(qsTr("Save OpenVPN config"), "amnezia_for_wireguard.conf", "*.conf", tfShareCode.textArea.text) - } - } - - Image { - Layout.topMargin: 20 - Layout.fillWidth: true - Layout.preferredHeight: width - smooth: false - source: ShareConnectionLogic.shareWireGuardQrCodeText - } - } - } -} diff --git a/client/ui/qml/Pages/Share/PageShareProtocolBase.qml b/client/ui/qml/Pages/Share/PageShareProtocolBase.qml deleted file mode 100644 index 603abdfa..00000000 --- a/client/ui/qml/Pages/Share/PageShareProtocolBase.qml +++ /dev/null @@ -1,19 +0,0 @@ -import QtQuick -import QtQuick.Controls -import PageEnum 1.0 -import ProtocolEnum 1.0 -import "./.." -import "../../Controls" -import "../../Config" - -PageBase { - id: root - property var protocol: ProtocolEnum.Any - page: PageEnum.ProtocolShare - logic: ShareConnectionLogic - - readonly property string generateConfigText: qsTr("Generate config") - readonly property string generatingConfigText: qsTr("Generating config...") - readonly property string showConfigText: qsTr("Show config") - property bool genConfigProcess: false -} diff --git a/client/ui/qml/main.qml b/client/ui/qml/main.qml deleted file mode 100644 index 615197f0..00000000 --- a/client/ui/qml/main.qml +++ /dev/null @@ -1,387 +0,0 @@ -import QtCore -import QtQuick -import QtQuick.Controls -import QtQuick.Controls.Basic -import QtQuick.Dialogs -import QtQuick.Layouts -import QtQuick.Window - -import Qt.labs.platform as LabsPlatform -import Qt.labs.folderlistmodel as LabsFolderlistmodel - -import PageEnum 1.0 -import PageType 1.0 - -import "Controls" -import "Pages" -import "Pages/Protocols" -import "Pages/Share" -import "Pages/ClientInfo" -import "Config" - -Window { - property var pages: ({}) - property var protocolPages: ({}) - property var sharePages: ({}) - property var clientInfoPages: ({}) - - id: root - visible: true - width: GC.screenWidth - height: GC.screenHeight - minimumWidth: GC.isDesktop() ? 360 : 0 - minimumHeight: GC.isDesktop() ? 640 : 0 - onClosing: function() { - console.debug("QML onClosing signal") - UiLogic.onCloseWindow() - } - - title: "AmneziaVPN" - - function gotoPage(type, page, reset, slide) { - - let p_obj; - if (type === PageType.Basic) p_obj = pages[page] - else if (type === PageType.Proto) p_obj = protocolPages[page] - else if (type === PageType.ShareProto) p_obj = sharePages[page] - else if (type === PageType.ClientInfo) p_obj = clientInfoPages[page] - else return - - //console.debug("QML gotoPage " + type + " " + page + " " + p_obj) - - if (pageLoader.depth > 0) { - pageLoader.currentItem.deactivated() - } - - if (slide) { - pageLoader.push(p_obj, {}, StackView.PushTransition) - } else { - pageLoader.push(p_obj, {}, StackView.Immediate) - } - - if (reset) { - p_obj.logic.onUpdatePage(); - } - - p_obj.activated(reset) - } - - function close_page() { - if (pageLoader.depth <= 1) { - if (GC.isMobile()) { - root.close() - } - return - } - - pageLoader.currentItem.deactivated() - pageLoader.pop() - } - - function set_start_page(page, slide) { - if (pageLoader.depth > 0) { - pageLoader.currentItem.deactivated() - } - - pageLoader.clear() - if (slide) { - pageLoader.push(pages[page], {}, StackView.PushTransition) - } else { - pageLoader.push(pages[page], {}, StackView.Immediate) - } - if (page === PageEnum.Start) { - UiLogic.pushButtonBackFromStartVisible = !pageLoader.empty - UiLogic.onUpdatePage(); - } - } - - Rectangle { - anchors.fill: parent - color: "white" - } - - StackView { - id: pageLoader - y: 0 - anchors.fill: parent - focus: true - - onCurrentItemChanged: function() { - UiLogic.currentPageValue = currentItem.page - } - - onDepthChanged: function() { - UiLogic.pagesStackDepth = depth - } - - Keys.onPressed: function(event) { - UiLogic.keyPressEvent(event.key) - event.accepted = true - } - } - - LabsFolderlistmodel.FolderListModel { - id: folderModelPages - folder: "qrc:/ui/qml/Pages/" - nameFilters: ["*.qml"] - showDirs: false - - onStatusChanged: if (status == LabsFolderlistmodel.FolderListModel.Ready) { - for (var i=0; i -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "amnezia_application.h" - -#include "configurators/cloak_configurator.h" -#include "configurators/openvpn_configurator.h" -#include "configurators/shadowsocks_configurator.h" -#include "configurators/ssh_configurator.h" -#include "configurators/vpn_configurator.h" - -#include "core/errorstrings.h" -#include "core/server_defs.h" -#include "core/servercontroller.h" - -#include "containers/containers_defs.h" - -#include "ui/qautostart.h" - -#include "logger.h" -#include "uilogic.h" -#include "utilities.h" -#include "version.h" -#include "vpnconnection.h" -#include - -#if defined Q_OS_MAC || defined Q_OS_LINUX - #include "ui/macos_util.h" -#endif - -#ifdef Q_OS_ANDROID - #include "platforms/android/android_controller.h" -#endif - -#include "platforms/ios/MobileUtils.h" - -#include "pages_logic/AdvancedServerSettingsLogic.h" -#include "pages_logic/AppSettingsLogic.h" -#include "pages_logic/ClientInfoLogic.h" -#include "pages_logic/ClientManagementLogic.h" -#include "pages_logic/GeneralSettingsLogic.h" -#include "pages_logic/NetworkSettingsLogic.h" -#include "pages_logic/NewServerProtocolsLogic.h" -#include "pages_logic/QrDecoderLogic.h" -#include "pages_logic/ServerConfiguringProgressLogic.h" -#include "pages_logic/ServerContainersLogic.h" -#include "pages_logic/ServerListLogic.h" -#include "pages_logic/ServerSettingsLogic.h" -#include "pages_logic/ShareConnectionLogic.h" -#include "pages_logic/SitesLogic.h" -#include "pages_logic/StartPageLogic.h" -#include "pages_logic/ViewConfigLogic.h" -#include "pages_logic/VpnLogic.h" -#include "pages_logic/WizardLogic.h" - -#include "pages_logic/protocols/CloakLogic.h" -#include "pages_logic/protocols/OpenVpnLogic.h" -#include "pages_logic/protocols/OtherProtocolsLogic.h" -#include "pages_logic/protocols/ShadowSocksLogic.h" -#include "pages_logic/protocols/WireGuardLogic.h" - -using namespace amnezia; -using namespace PageEnumNS; - -UiLogic::UiLogic(std::shared_ptr settings, std::shared_ptr configurator, QObject *parent) - : QObject(parent), m_settings(settings), m_configurator(configurator) -{ - m_protocolsModel = new ProtocolsModel(settings, this); - m_clientManagementModel = new ClientManagementModel(this); - m_vpnConnection = new VpnConnection(settings, configurator); - m_vpnConnection->moveToThread(&m_vpnConnectionThread); - m_vpnConnectionThread.start(); - - m_protocolLogicMap.insert(Proto::OpenVpn, new OpenVpnLogic(this)); - m_protocolLogicMap.insert(Proto::ShadowSocks, new ShadowSocksLogic(this)); - m_protocolLogicMap.insert(Proto::Cloak, new CloakLogic(this)); - m_protocolLogicMap.insert(Proto::WireGuard, new WireGuardLogic(this)); - - m_protocolLogicMap.insert(Proto::Dns, new OtherProtocolsLogic(this)); - m_protocolLogicMap.insert(Proto::Sftp, new OtherProtocolsLogic(this)); - m_protocolLogicMap.insert(Proto::TorWebSite, new OtherProtocolsLogic(this)); -} - -UiLogic::~UiLogic() -{ - emit hide(); - -#ifdef AMNEZIA_DESKTOP - if (m_vpnConnection->connectionState() != Vpn::ConnectionState::Disconnected) { - m_vpnConnection->disconnectFromVpn(); - for (int i = 0; i < 50; i++) { - qApp->processEvents(QEventLoop::ExcludeUserInputEvents); - QThread::msleep(100); - if (m_vpnConnection->isDisconnected()) { - break; - } - } - } -#endif - - m_vpnConnection->deleteLater(); - m_vpnConnectionThread.quit(); - m_vpnConnectionThread.wait(3000); - - qDebug() << "Application closed"; -} - -void UiLogic::initializeUiLogic() -{ -#ifdef Q_OS_ANDROID - connect(AndroidController::instance(), &AndroidController::initialized, - [this](bool status, bool connected, const QDateTime &connectionDate) { - if (connected) { - pageLogic()->onConnectionStateChanged(Vpn::ConnectionState::Connected); - if (m_vpnConnection) - m_vpnConnection->restoreConnection(); - } - }); -// if (!AndroidController::instance()->initialize(pageLogic())) { -// qCritical() << QString("Init failed"); -// if (m_vpnConnection) m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Error); -// return; -// } -#endif - - m_notificationHandler = NotificationHandler::create(qmlRoot()); - - connect(m_vpnConnection, &VpnConnection::connectionStateChanged, m_notificationHandler, - &NotificationHandler::setConnectionState); - connect(m_notificationHandler, &NotificationHandler::raiseRequested, this, &UiLogic::raise); - connect(m_notificationHandler, &NotificationHandler::connectRequested, pageLogic(), &VpnLogic::onConnect); - connect(m_notificationHandler, &NotificationHandler::disconnectRequested, pageLogic(), - &VpnLogic::onDisconnect); - - // if (m_settings->serversCount() > 0) { - // if (m_settings->defaultServerIndex() < 0) m_settings->setDefaultServer(0); - // emit goToPage(Page::PageStart, true, false); - // } - // else { - // emit goToPage(Page::PageSetupWizardStart, true, false); - // } - - m_selectedServerIndex = m_settings->defaultServerIndex(); -} - -void UiLogic::showOnStartup() -{ - if (!m_settings->isStartMinimized()) { - emit show(); - } else { -#ifdef Q_OS_WIN - emit hide(); -#elif defined Q_OS_MACX - // TODO: fix: setDockIconVisible(false); -#endif - } -} - -void UiLogic::onUpdateAllPages() -{ - for (auto logic : m_logicMap) { - if (dynamic_cast(logic) || dynamic_cast(logic) - || dynamic_cast(logic)) { - continue; - } - logic->onUpdatePage(); - } -} - -void UiLogic::keyPressEvent(Qt::Key key) -{ - switch (key) { - case Qt::Key_AsciiTilde: - case Qt::Key_QuoteLeft: emit toggleLogPanel(); break; - case Qt::Key_L: Logger::openLogsFolder(); break; - case Qt::Key_K: Logger::openServiceLogsFolder(); break; -#ifdef QT_DEBUG - case Qt::Key_Q: qApp->quit(); break; - case Qt::Key_H: - m_selectedServerIndex = m_settings->defaultServerIndex(); - m_selectedDockerContainer = m_settings->defaultContainer(m_selectedServerIndex); - - // updateSharingPage(selectedServerIndex, m_settings->serverCredentials(selectedServerIndex), selectedDockerContainer); - emit goToPage(Page::ShareConnection); - break; -#endif - case Qt::Key_C: - qDebug().noquote() << "Def server" << m_settings->defaultServerIndex() - << m_settings->defaultContainerName(m_settings->defaultServerIndex()); - qDebug().noquote() << QJsonDocument(m_settings->defaultServer()).toJson(); - break; - case Qt::Key_A: emit goToPage(Page::Start); break; - case Qt::Key_S: - m_selectedServerIndex = m_settings->defaultServerIndex(); - emit goToPage(Page::ServerSettings); - break; - case Qt::Key_P: onGotoCurrentProtocolsPage(); break; - case Qt::Key_T: - m_configurator->sshConfigurator->openSshTerminal(m_settings->serverCredentials(m_settings->defaultServerIndex())); - break; - case Qt::Key_Escape: - if (currentPage() == Page::Vpn) - break; - if (currentPage() == Page::ServerConfiguringProgress) - break; - case Qt::Key_Back: - - // if (currentPage() == Page::Start && pagesStack.size() < 2) break; - // if (currentPage() == Page::Sites && - // ui->tableView_sites->selectionModel()->selection().indexes().size() > 0) { - // ui->tableView_sites->clearSelection(); - // break; - // } - - emit closePage(); - //} - default:; - } -} - -void UiLogic::onCloseWindow() -{ -#ifdef Q_OS_ANDROID - qApp->quit(); -#else - if (m_settings->serversCount() == 0) { - qApp->quit(); - } else { - emit hide(); - } -#endif -} - -QString UiLogic::containerName(int container) -{ - return ContainerProps::containerHumanNames().value(static_cast(container)); -} - -QString UiLogic::containerDesc(int container) -{ - return ContainerProps::containerDescriptions().value(static_cast(container)); -} - -void UiLogic::onGotoCurrentProtocolsPage() -{ - m_selectedServerIndex = m_settings->defaultServerIndex(); - m_selectedDockerContainer = m_settings->defaultContainer(m_selectedServerIndex); - emit goToPage(Page::ServerContainers); -} - -void UiLogic::installServer(QPair &container) -{ - emit goToPage(Page::ServerConfiguringProgress); - QEventLoop loop; - QTimer::singleShot(500, &loop, SLOT(quit())); - loop.exec(); - qApp->processEvents(); - - ServerConfiguringProgressLogic::PageFunc pageFunc; - pageFunc.setEnabledFunc = [this](bool enabled) -> void { - pageLogic()->set_pageEnabled(enabled); - }; - - ServerConfiguringProgressLogic::ButtonFunc noButton; - - ServerConfiguringProgressLogic::LabelFunc waitInfoFunc; - waitInfoFunc.setTextFunc = [this](const QString &text) -> void { - pageLogic()->set_labelWaitInfoText(text); - }; - waitInfoFunc.setVisibleFunc = [this](bool visible) -> void { - pageLogic()->set_labelWaitInfoVisible(visible); - }; - - ServerConfiguringProgressLogic::ProgressFunc progressBarFunc; - progressBarFunc.setVisibleFunc = [this](bool visible) -> void { - pageLogic()->set_progressBarVisible(visible); - }; - progressBarFunc.setValueFunc = [this](int value) -> void { - pageLogic()->set_progressBarValue(value); - }; - progressBarFunc.getValueFunc = [this](void) -> int { - return pageLogic()->progressBarValue(); - }; - progressBarFunc.getMaximumFunc = [this](void) -> int { - return pageLogic()->progressBarMaximum(); - }; - progressBarFunc.setTextVisibleFunc = [this](bool visible) -> void { - pageLogic()->set_progressBarTextVisible(visible); - }; - progressBarFunc.setTextFunc = [this](const QString &text) -> void { - pageLogic()->set_progressBarText(text); - }; - - ServerConfiguringProgressLogic::LabelFunc busyInfoFunc; - busyInfoFunc.setTextFunc = [this](const QString &text) -> void { - pageLogic()->set_labelServerBusyText(text); - }; - busyInfoFunc.setVisibleFunc = [this](bool visible) -> void { - pageLogic()->set_labelServerBusyVisible(visible); - }; - - ServerConfiguringProgressLogic::ButtonFunc cancelButtonFunc; - cancelButtonFunc.setVisibleFunc = [this](bool visible) -> void { - pageLogic()->set_pushButtonCancelVisible(visible); - }; - - bool isServerCreated = false; - ErrorCode errorCode = addAlreadyInstalledContainersGui(isServerCreated); - if (errorCode == ErrorCode::NoError) { - if (!isContainerAlreadyAddedToGui(container.first)) { - progressBarFunc.setTextFunc(QString("Installing %1").arg(ContainerProps::containerToString(container.first))); - auto installAction = [&]() { - ServerController serverController(m_settings); - return serverController.setupContainer(m_installCredentials, container.first, container.second); - }; - errorCode = pageLogic()->doInstallAction( - installAction, pageFunc, progressBarFunc, noButton, waitInfoFunc, busyInfoFunc, cancelButtonFunc); - if (errorCode == ErrorCode::NoError) { - if (!isServerCreated) { - QJsonObject server; - server.insert(config_key::hostName, m_installCredentials.hostName); - server.insert(config_key::userName, m_installCredentials.userName); - server.insert(config_key::password, m_installCredentials.secretData); - server.insert(config_key::port, m_installCredentials.port); - server.insert(config_key::description, m_settings->nextAvailableServerName()); - - server.insert(config_key::containers, QJsonArray { container.second }); - server.insert(config_key::defaultContainer, ContainerProps::containerToString(container.first)); - - m_settings->addServer(server); - m_settings->setDefaultServer(m_settings->serversCount() - 1); - } else { - m_settings->setContainerConfig(m_settings->serversCount() - 1, container.first, container.second); - m_settings->setDefaultContainer(m_settings->serversCount() - 1, container.first); - } - onUpdateAllPages(); - - emit setStartPage(Page::Vpn); - qApp->processEvents(); - return; - } - } else { - onUpdateAllPages(); - emit showWarningMessage( - "Attention! The container you are trying to install is already installed on the server. " - "All installed containers have been added to the application "); - emit setStartPage(Page::Vpn); - return; - } - } - emit showWarningMessage(tr("Error occurred while configuring server.") + "\n" + tr("Error message: ") - + errorString(errorCode) + "\n" + tr("See logs for details.")); - emit closePage(); -} - -PageProtocolLogicBase *UiLogic::protocolLogic(Proto p) -{ - PageProtocolLogicBase *logic = m_protocolLogicMap.value(p); - if (logic) - return logic; - else { - qCritical() << "UiLogic::protocolLogic Warning: logic missing for" << p; - return new PageProtocolLogicBase(this); - } -} - -QObject *UiLogic::qmlRoot() const -{ - return m_qmlRoot; -} - -void UiLogic::setQmlRoot(QObject *newQmlRoot) -{ - m_qmlRoot = newQmlRoot; -} - -NotificationHandler *UiLogic::notificationHandler() const -{ - return m_notificationHandler; -} - -void UiLogic::setQmlContextProperty(PageLogicBase *logic) -{ - amnApp->qmlEngine()->rootContext()->setContextProperty(logic->metaObject()->className(), logic); -} - -PageEnumNS::Page UiLogic::currentPage() -{ - return static_cast(currentPageValue()); -} - -void UiLogic::saveTextFile(const QString &desc, const QString &suggestedName, QString ext, const QString &data) -{ -#ifdef Q_OS_IOS - shareTempFile(suggestedName, ext, data); - return; -#endif - - ext.replace("*", ""); - QString docDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); - QUrl fileName; -#ifdef AMNEZIA_DESKTOP - fileName = QFileDialog::getSaveFileUrl(nullptr, desc, QUrl::fromLocalFile(docDir + "/" + suggestedName), "*" + ext); - if (fileName.isEmpty()) - return; - if (!fileName.toString().endsWith(ext)) - fileName = QUrl(fileName.toString() + ext); -#elif defined Q_OS_ANDROID - qDebug() << "UiLogic::shareConfig" << data; - AndroidController::instance()->shareConfig(data, suggestedName); - return; -#endif - - if (fileName.isEmpty()) - return; - -#ifdef AMNEZIA_DESKTOP - QFile save(fileName.toLocalFile()); -#else - QFile save(QQmlFile::urlToLocalFileOrQrc(fileName)); -#endif - - save.open(QIODevice::WriteOnly); - save.write(data.toUtf8()); - save.close(); - - QFileInfo fi(fileName.toLocalFile()); - QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); -} - -void UiLogic::saveBinaryFile(const QString &desc, QString ext, const QString &data) -{ - ext.replace("*", ""); - QString fileName = QFileDialog::getSaveFileName( - nullptr, desc, QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), "*" + ext); - - if (fileName.isEmpty()) - return; - if (!fileName.endsWith(ext)) - fileName.append(ext); - - QFile save(fileName); - save.open(QIODevice::WriteOnly); - save.write(QByteArray::fromBase64(data.toUtf8())); - save.close(); - - QFileInfo fi(fileName); - QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); -} - -void UiLogic::copyToClipboard(const QString &text) -{ - qApp->clipboard()->setText(text); -} - -void UiLogic::shareTempFile(const QString &suggestedName, QString ext, const QString &data) -{ - ext.replace("*", ""); - QString fileName = QDir::tempPath() + "/" + suggestedName; - - if (fileName.isEmpty()) - return; - if (!fileName.endsWith(ext)) - fileName.append(ext); - - QFile::remove(fileName); - - QFile save(fileName); - save.open(QIODevice::WriteOnly); - save.write(data.toUtf8()); - save.close(); - - QStringList filesToSend; - filesToSend.append(fileName); - MobileUtils::shareText(filesToSend); -} - -QString UiLogic::getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, - QString *selectedFilter, QFileDialog::Options options) -{ - QString fileName = QFileDialog::getOpenFileName(parent, caption, dir, filter, selectedFilter, options); - -#ifdef Q_OS_ANDROID - // patch for files containing spaces etc - const QString sep { "raw%3A%2F" }; - if (fileName.startsWith("content://") && fileName.contains(sep)) { - QString contentUrl = fileName.split(sep).at(0); - QString rawUrl = fileName.split(sep).at(1); - rawUrl.replace(" ", "%20"); - fileName = contentUrl + sep + rawUrl; - } -#endif - return fileName; -} - -void UiLogic::registerPagesLogic() -{ - amnApp->qmlEngine()->rootContext()->setContextProperty("UiLogic", this); - - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); - registerPageLogic(); -} - -ErrorCode UiLogic::addAlreadyInstalledContainersGui(bool &isServerCreated) -{ - isServerCreated = false; - ServerCredentials installCredentials = m_installCredentials; - bool createNewServer = true; - int serverIndex; - - for (int i = 0; i < m_settings->serversCount(); i++) { - const ServerCredentials credentials = m_settings->serverCredentials(i); - if (m_installCredentials.hostName == credentials.hostName && m_installCredentials.port == credentials.port) { - createNewServer = false; - isServerCreated = true; - installCredentials = credentials; - serverIndex = i; - break; - } - } - - QMap installedContainers; - ServerController serverController(m_settings); - ErrorCode errorCode = serverController.getAlreadyInstalledContainers(installCredentials, installedContainers); - if (errorCode != ErrorCode::NoError) { - return errorCode; - } - - if (!installedContainers.empty()) { - QJsonObject server; - QJsonArray containerConfigs; - if (createNewServer) { - server.insert(config_key::hostName, installCredentials.hostName); - server.insert(config_key::userName, installCredentials.userName); - server.insert(config_key::password, installCredentials.secretData); - server.insert(config_key::port, installCredentials.port); - server.insert(config_key::description, m_settings->nextAvailableServerName()); - } - - for (auto container = installedContainers.begin(); container != installedContainers.end(); container++) { - if (isContainerAlreadyAddedToGui(container.key())) { - continue; - } - - if (createNewServer) { - containerConfigs.append(container.value()); - server.insert(config_key::containers, containerConfigs); - } else { - m_settings->setContainerConfig(serverIndex, container.key(), container.value()); - m_settings->setDefaultContainer(serverIndex, installedContainers.firstKey()); - } - } - - if (createNewServer) { - server.insert(config_key::defaultContainer, - ContainerProps::containerToString(installedContainers.firstKey())); - m_settings->addServer(server); - m_settings->setDefaultServer(m_settings->serversCount() - 1); - isServerCreated = true; - } - } - - return ErrorCode::NoError; -} - -bool UiLogic::isContainerAlreadyAddedToGui(DockerContainer container) -{ - for (int i = 0; i < m_settings->serversCount(); i++) { - const ServerCredentials credentials = m_settings->serverCredentials(i); - if (m_installCredentials.hostName == credentials.hostName && m_installCredentials.port == credentials.port) { - const QJsonObject containerConfig = m_settings->containerConfig(i, container); - if (!containerConfig.isEmpty()) { - return true; - } - } - } - return false; -} diff --git a/client/ui/uilogic.h b/client/ui/uilogic.h deleted file mode 100644 index 081b8e5a..00000000 --- a/client/ui/uilogic.h +++ /dev/null @@ -1,201 +0,0 @@ -#ifndef UILOGIC_H -#define UILOGIC_H - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "property_helper.h" -#include "pages.h" -#include "protocols/vpnprotocol.h" -#include "containers/containers_defs.h" - -#include "models/containers_model.h" -#include "models/protocols_model.h" -#include "models/clientManagementModel.h" - -#include "notificationhandler.h" - -class Settings; -class VpnConfigurator; -class ServerController; - -class PageLogicBase; - -class AppSettingsLogic; -class GeneralSettingsLogic; -class NetworkSettingsLogic; -class NewServerProtocolsLogic; -class QrDecoderLogic; -class ServerConfiguringProgressLogic; -class ServerListLogic; -class ServerSettingsLogic; -class ServerContainersLogic; -class ShareConnectionLogic; -class SitesLogic; -class StartPageLogic; -class ViewConfigLogic; -class VpnLogic; -class WizardLogic; -class ClientManagementLogic; -class ClientInfoLogic; -class AdvancedServerSettingsLogic; - -class PageProtocolLogicBase; -class OpenVpnLogic; -class ShadowSocksLogic; -class CloakLogic; - -class OtherProtocolsLogic; - -class VpnConnection; - -class CreateServerTest; - -class UiLogic : public QObject -{ - Q_OBJECT - - AUTO_PROPERTY(bool, pageEnabled) - AUTO_PROPERTY(int, pagesStackDepth) - AUTO_PROPERTY(int, currentPageValue) - - READONLY_PROPERTY(QObject *, protocolsModel) - READONLY_PROPERTY(QObject *, clientManagementModel) - -public: - explicit UiLogic(std::shared_ptr settings, std::shared_ptr configurator, QObject *parent = nullptr); - ~UiLogic(); - void showOnStartup(); - - friend class PageLogicBase; - - friend class AppSettingsLogic; - friend class GeneralSettingsLogic; - friend class NetworkSettingsLogic; - friend class ServerConfiguringProgressLogic; - friend class NewServerProtocolsLogic; - friend class ServerListLogic; - friend class ServerSettingsLogic; - friend class ServerContainersLogic; - friend class ShareConnectionLogic; - friend class SitesLogic; - friend class StartPageLogic; - friend class ViewConfigLogic; - friend class VpnLogic; - friend class WizardLogic; - friend class ClientManagementLogic; - friend class ClientInfoLogic; - friend class AdvancedServerSettingsLogic; - - friend class PageProtocolLogicBase; - friend class OpenVpnLogic; - friend class ShadowSocksLogic; - friend class CloakLogic; - - friend class OtherProtocolsLogic; - - friend class CreateServerTest; - - Q_INVOKABLE virtual void onUpdatePage() {} // UiLogic is set as logic class for some qml pages - Q_INVOKABLE void onUpdateAllPages(); - - Q_INVOKABLE void initializeUiLogic(); - Q_INVOKABLE void onCloseWindow(); - - Q_INVOKABLE QString containerName(int container); - Q_INVOKABLE QString containerDesc(int container); - - Q_INVOKABLE void onGotoCurrentProtocolsPage(); - - Q_INVOKABLE void keyPressEvent(Qt::Key key); - - Q_INVOKABLE void saveTextFile(const QString& desc, const QString &suggestedName, QString ext, const QString& data); - Q_INVOKABLE void saveBinaryFile(const QString& desc, QString ext, const QString& data); - Q_INVOKABLE void copyToClipboard(const QString& text); - - Q_INVOKABLE amnezia::ErrorCode addAlreadyInstalledContainersGui(bool &isServerCreated); - - void shareTempFile(const QString &suggestedName, QString ext, const QString& data); - static QString getOpenFileName(QWidget *parent = nullptr, - const QString &caption = QString(), - const QString &dir = QString(), - const QString &filter = QString(), - QString *selectedFilter = nullptr, - QFileDialog::Options options = QFileDialog::Options()); -signals: - void goToPage(PageEnumNS::Page page, bool reset = true, bool slide = true); - void goToProtocolPage(Proto protocol, bool reset = true, bool slide = true); - void goToShareProtocolPage(Proto protocol, bool reset = true, bool slide = true); - void goToClientInfoPage(Proto protocol, bool reset = true, bool slide = true); - - void closePage(); - void setStartPage(PageEnumNS::Page page, bool slide = true); - void showPublicKeyWarning(); - void showConnectErrorDialog(); - void show(); - void hide(); - void raise(); - void toggleLogPanel(); - void showWarningMessage(QString message); - -private slots: - // containers - INOUT arg - void installServer(QPair &container); - -private: - PageEnumNS::Page currentPage(); - bool isContainerAlreadyAddedToGui(DockerContainer container); - -public: - Q_INVOKABLE PageProtocolLogicBase *protocolLogic(Proto p); - - QObject *qmlRoot() const; - void setQmlRoot(QObject *newQmlRoot); - - NotificationHandler *notificationHandler() const; - - void setQmlContextProperty(PageLogicBase *logic); - void registerPagesLogic(); - - template - void registerPageLogic() - { - T* logic = new T(this); - m_logicMap[std::type_index(typeid(T))] = logic; - setQmlContextProperty(logic); - } - - template - T* pageLogic() - { - return static_cast(m_logicMap.value(std::type_index(typeid(T)))); - } - -private: - QObject *m_qmlRoot{nullptr}; - - QMap m_logicMap; - - QMap m_protocolLogicMap; - - VpnConnection* m_vpnConnection; - QThread m_vpnConnectionThread; - - std::shared_ptr m_settings; - std::shared_ptr m_configurator; - - NotificationHandler* m_notificationHandler; - - int m_selectedServerIndex = -1; // server index to use when proto settings page opened - DockerContainer m_selectedDockerContainer; // same - ServerCredentials m_installCredentials; // used to save cred between pages new_server and new_server_protocols and wizard -}; -#endif // UILOGIC_H From cbcf18781415963b689da1e54746798ea1544c2c Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 31 Aug 2023 21:49:36 +0500 Subject: [PATCH 083/278] added missing include files --- client/configurators/openvpn_configurator.cpp | 5 +++++ client/fileUtilites.cpp | 1 + client/ui/controllers/installController.cpp | 1 + client/ui/qml/Pages2/PageServiceSftpSettings.qml | 2 +- 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/client/configurators/openvpn_configurator.cpp b/client/configurators/openvpn_configurator.cpp index bfde4a91..a62bdd9c 100644 --- a/client/configurators/openvpn_configurator.cpp +++ b/client/configurators/openvpn_configurator.cpp @@ -7,6 +7,11 @@ #include #include #include +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) + #include +#else + #include +#endif #include "containers/containers_defs.h" #include "core/scripts_registry.h" diff --git a/client/fileUtilites.cpp b/client/fileUtilites.cpp index b3c65216..4eedfc9a 100644 --- a/client/fileUtilites.cpp +++ b/client/fileUtilites.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #ifdef Q_OS_ANDROID #include "platforms/android/android_controller.h" diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 7d6ba590..0a3da475 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -1,6 +1,7 @@ #include "installController.h" #include +#include #include #include #include diff --git a/client/ui/qml/Pages2/PageServiceSftpSettings.qml b/client/ui/qml/Pages2/PageServiceSftpSettings.qml index 32c0c170..5eb288f8 100644 --- a/client/ui/qml/Pages2/PageServiceSftpSettings.qml +++ b/client/ui/qml/Pages2/PageServiceSftpSettings.qml @@ -253,7 +253,7 @@ PageType { text: qsTr("Remove SFTP and all data stored there") onClicked: { - questionDrawer.headerText = qsTr("Some description") + questionDrawer.headerText = qsTr("Remove SFTP and all data stored there?") questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.noButtonText = qsTr("Cancel") From b58295d1d68d9756a61e48bf217a6a70c4d3c2e6 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 1 Sep 2023 00:48:58 +0500 Subject: [PATCH 084/278] added the ability to restore settings from backup on the initial screen - fixed the display of services in the settings for mobile devices --- client/fileUtilites.cpp | 2 +- .../qml/Components/HomeContainersListView.qml | 6 ++++++ .../ui/qml/Filters/ContainersModelFilters.qml | 8 ++++---- .../ui/qml/Pages2/PageSettingsServerData.qml | 2 +- .../qml/Pages2/PageSettingsSplitTunneling.qml | 6 ++++++ .../Pages2/PageSetupWizardConfigSource.qml | 14 ++++++++++---- client/ui/qml/Pages2/PageSetupWizardStart.qml | 19 +++++++++++++++++++ client/ui/qml/main2.qml | 4 +--- 8 files changed, 48 insertions(+), 13 deletions(-) diff --git a/client/fileUtilites.cpp b/client/fileUtilites.cpp index 4eedfc9a..96f1c741 100644 --- a/client/fileUtilites.cpp +++ b/client/fileUtilites.cpp @@ -81,5 +81,5 @@ QString FileUtilites::getFileName(QString fileName) return fileName; #endif - return QUrl(FileUtilites::getFileName(fileName)).toLocalFile(); + return QUrl(fileName).toLocalFile(); } diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index 0c2408be..fc4dd8b3 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -53,6 +53,12 @@ ListView { checkable: isInstalled checked: isDefault + onPressed: function(mouse) { + if (!isSupported) { + PageController.showErrorMessage(qsTr("The selected protocol is not supported on the current platform")) + } + } + onClicked: { if (checked) { isDefault = true diff --git a/client/ui/qml/Filters/ContainersModelFilters.qml b/client/ui/qml/Filters/ContainersModelFilters.qml index fa8bd83b..dd6d9525 100644 --- a/client/ui/qml/Filters/ContainersModelFilters.qml +++ b/client/ui/qml/Filters/ContainersModelFilters.qml @@ -32,16 +32,16 @@ Item { } function getWriteAccessProtocolsListFilters() { - return [vpnTypeFilter, supportedFilter] + return [vpnTypeFilter] } function getReadAccessProtocolsListFilters() { - return [vpnTypeFilter, supportedFilter, installedFilter] + return [vpnTypeFilter, installedFilter] } function getWriteAccessServicesListFilters() { - return [serviceTypeFilter, supportedFilter] + return [serviceTypeFilter } function getReadAccessServicesListFilters() { - return [serviceTypeFilter, supportedFilter, installedFilter] + return [serviceTypeFilter, installedFilter] } } diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 15c5e531..8ae90351 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -22,7 +22,7 @@ PageType { if (isInstalledContainerFound) { message = qsTr("All installed containers have been added to the application") } else { - message = qsTr("No installed containers found") + message = qsTr("No new installed containers found") } PageController.showErrorMessage(message) diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index 535ab18c..e074b4ce 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -241,8 +241,10 @@ PageType { buttonImageSource: "qrc:/images/controls/plus.svg" clickedFunc: function() { + PageController.showBusyIndicator(true) SitesController.addSite(textFieldText) textFieldText = "" + PageController.showBusyIndicator(false) } } @@ -312,8 +314,10 @@ PageType { currentFile: StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/sites" defaultSuffix: ".json" onAccepted: { + PageController.showBusyIndicator(true) SitesController.exportSites(saveFileDialog.currentFile.toString()) moreActionsDrawer.close() + PageController.showBusyIndicator(false) } } } @@ -394,9 +398,11 @@ PageType { acceptLabel: qsTr("Open sites file") nameFilters: [ "Sites files (*.json)" ] onAccepted: { + PageController.showBusyIndicator(true) SitesController.importSites(openFileDialog.selectedFile.toString(), replaceExistingSites) importSitesDrawer.close() moreActionsDrawer.close() + PageController.showBusyIndicator(false) } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index de8b8c87..6362ca6d 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -65,7 +65,7 @@ It's okay as long as it's from someone you trust.") Layout.fillWidth: true Layout.topMargin: 16 - text: qsTr("File with connection settings") + text: qsTr("File with connection settings or backup") rightImageSource: "qrc:/images/controls/chevron-right.svg" leftImageSource: "qrc:/images/controls/folder-open.svg" @@ -76,10 +76,16 @@ It's okay as long as it's from someone you trust.") FileDialog { id: fileDialog acceptLabel: qsTr("Open config file") - nameFilters: [ "Config files (*.vpn *.ovpn *.conf)" ] + nameFilters: [ "Config or backup files (*.vpn *.ovpn *.conf *.backup)" ] onAccepted: { - ImportController.extractConfigFromFile(fileDialog.selectedFile.toString()) - goToPage(PageEnum.PageSetupWizardViewConfig) + if (fileDialog.selectedFile.toString().indexOf(".backup") != -1) { + PageController.showBusyIndicator(true) + SettingsController.restoreAppConfig(fileDialog.selectedFile.toString()) + PageController.showBusyIndicator(false) + } else { + ImportController.extractConfigFromFile(fileDialog.selectedFile.toString()) + goToPage(PageEnum.PageSetupWizardViewConfig) + } } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index 11d7ba29..3bc57495 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -19,6 +19,19 @@ PageType { function onGoToPageViewConfig() { goToPage(PageEnum.PageSetupWizardViewConfig) } + + function onShowBusyIndicator(visible) { + busyIndicator.visible = visible + } + } + + Connections { + target: SettingsController + + function onRestoreBackupFinished() { + PageController.showNotificationMessage(qsTr("Settings restored from backup file")) + PageController.replaceStartPage() + } } FlickableType { @@ -93,4 +106,10 @@ PageType { id: connectionTypeSelection } } + + BusyIndicatorType { + id: busyIndicator + anchors.centerIn: parent + z: 1 + } } diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index 6ae434d7..d3c3aa1b 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -47,9 +47,7 @@ Window { function onReplaceStartPage() { var pagePath = PageController.getInitialPage() - while (rootStackView.depth > 1) { - rootStackView.pop() - } + rootStackView.clear() PageController.updateNavigationBarColor(PageController.getInitialPageNavigationBarColor()) rootStackView.replace(pagePath, { "objectName" : pagePath }) } From 4e9f68acff8b95cb890b61ef3d3000cccab4d8c0 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 1 Sep 2023 02:07:52 +0500 Subject: [PATCH 085/278] returned the lost comma --- client/ui/qml/Filters/ContainersModelFilters.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ui/qml/Filters/ContainersModelFilters.qml b/client/ui/qml/Filters/ContainersModelFilters.qml index dd6d9525..8c51c7ee 100644 --- a/client/ui/qml/Filters/ContainersModelFilters.qml +++ b/client/ui/qml/Filters/ContainersModelFilters.qml @@ -39,7 +39,7 @@ Item { } function getWriteAccessServicesListFilters() { - return [serviceTypeFilter + return [serviceTypeFilter] } function getReadAccessServicesListFilters() { return [serviceTypeFilter, installedFilter] From cacf74af3c537afe6db507d3f6468e7c613e3038 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Fri, 1 Sep 2023 01:08:44 +0300 Subject: [PATCH 086/278] Fix iOS build --- client/amnezia_application.cpp | 1 + client/fileUtilites.cpp | 11 ++--------- client/platforms/ios/QtAppDelegate-C-Interface.h | 4 ++-- client/platforms/ios/QtAppDelegate.h | 4 ++-- client/platforms/ios/QtAppDelegate.mm | 6 +++--- client/ui/controllers/importController.cpp | 2 +- client/ui/controllers/importController.h | 2 +- client/ui/macos_util.h | 3 +++ 8 files changed, 15 insertions(+), 18 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index d14917f0..d42d4cf9 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -112,6 +112,7 @@ void AmneziaApplication::init() #ifdef Q_OS_IOS IosController::Instance()->initialize(); + setImportController(m_importController.get()); #endif m_notificationHandler.reset(NotificationHandler::create(nullptr)); diff --git a/client/fileUtilites.cpp b/client/fileUtilites.cpp index 96f1c741..9cbc5a10 100644 --- a/client/fileUtilites.cpp +++ b/client/fileUtilites.cpp @@ -40,17 +40,10 @@ void FileUtilites::saveFile(QString fileName, const QString &data) filesToSend.append(fileName); MobileUtils::shareText(filesToSend); return; -#endif - -#ifdef Q_OS_IOS - QStringList filesToSend; - filesToSend.append(fileUrl.toString()); - MobileUtils::shareText(filesToSend); - return; -#endif - +#else QFileInfo fi(fileUrl.toLocalFile()); QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); +#endif } QString FileUtilites::getFileName(QString fileName) diff --git a/client/platforms/ios/QtAppDelegate-C-Interface.h b/client/platforms/ios/QtAppDelegate-C-Interface.h index afd31e7d..dd37dd2a 100644 --- a/client/platforms/ios/QtAppDelegate-C-Interface.h +++ b/client/platforms/ios/QtAppDelegate-C-Interface.h @@ -1,9 +1,9 @@ #ifndef QTAPPDELEGATECINTERFACE_H #define QTAPPDELEGATECINTERFACE_H -#include "ui/pages_logic/StartPageLogic.h" +#include "ui/controllers/importController.h" void QtAppDelegateInitialize(); -void setStartPageLogic(StartPageLogic*); +void setImportController(ImportController*); #endif // QTAPPDELEGATECINTERFACE_H diff --git a/client/platforms/ios/QtAppDelegate.h b/client/platforms/ios/QtAppDelegate.h index 1e0dc412..a32f1b64 100644 --- a/client/platforms/ios/QtAppDelegate.h +++ b/client/platforms/ios/QtAppDelegate.h @@ -1,9 +1,9 @@ #import #import "QtAppDelegate-C-Interface.h" -#include "ui/pages_logic/StartPageLogic.h" +#include "ui/controllers/importController.h" @interface QtAppDelegate : UIResponder +(QtAppDelegate *)sharedQtAppDelegate; -@property (nonatomic) StartPageLogic* startPageLogic; +@property (nonatomic) ImportController* ImportController; @end diff --git a/client/platforms/ios/QtAppDelegate.mm b/client/platforms/ios/QtAppDelegate.mm index f65856d9..432fc172 100644 --- a/client/platforms/ios/QtAppDelegate.mm +++ b/client/platforms/ios/QtAppDelegate.mm @@ -79,7 +79,7 @@ bool isOpenFile = file.open(QIODevice::ReadOnly); QByteArray data = file.readAll(); - [QtAppDelegate sharedQtAppDelegate].startPageLogic->importAnyFile(QString(data)); + [QtAppDelegate sharedQtAppDelegate].ImportController->extractConfigFromData(QString(data)); return YES; } return NO; @@ -92,8 +92,8 @@ void QtAppDelegateInitialize() NSLog(@"Created a new AppDelegate"); } -void setStartPageLogic(StartPageLogic* startPage) { - [QtAppDelegate sharedQtAppDelegate].startPageLogic = startPage; +void setImportController(ImportController* controller) { + [QtAppDelegate sharedQtAppDelegate].ImportController = controller; } @end diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 9a374aa1..b76302c5 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -97,7 +97,7 @@ void ImportController::extractConfigFromFile(const QString &fileName) } } -void ImportController::extractConfigFromData(QString &data) +void ImportController::extractConfigFromData(QString data) { auto configFormat = checkConfigFormat(data); if (configFormat == ConfigTypes::OpenVpn) { diff --git a/client/ui/controllers/importController.h b/client/ui/controllers/importController.h index 7def7733..1f8f8bbb 100644 --- a/client/ui/controllers/importController.h +++ b/client/ui/controllers/importController.h @@ -22,7 +22,7 @@ public: public slots: void importConfig(); void extractConfigFromFile(const QString &fileName); - void extractConfigFromData(QString &data); + void extractConfigFromData(QString data); void extractConfigFromCode(QString code); bool extractConfigFromQr(const QByteArray &data); QString getConfig(); diff --git a/client/ui/macos_util.h b/client/ui/macos_util.h index f5add902..15677e42 100644 --- a/client/ui/macos_util.h +++ b/client/ui/macos_util.h @@ -1,9 +1,12 @@ #ifndef OSXUTIL_H #define OSXUTIL_H + +#ifndef Q_OS_IOS #include #include void setDockIconVisible(bool visible); void fixWidget(QWidget *widget); +#endif #endif From 1b3a32f83f2cc7f64ff487c7e19d017871dd07f9 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Fri, 1 Sep 2023 14:49:10 +0300 Subject: [PATCH 087/278] Import config from filesystem on iOS --- client/amnezia_application.cpp | 6 ++++-- client/platforms/ios/QtAppDelegate-C-Interface.h | 1 - client/platforms/ios/QtAppDelegate.h | 3 --- client/platforms/ios/QtAppDelegate.mm | 7 ++----- client/platforms/ios/ios_controller.h | 1 + 5 files changed, 7 insertions(+), 11 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index d42d4cf9..64adb1d1 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -21,7 +21,6 @@ #include "protocols/qml_register_protocols.h" #if defined(Q_OS_IOS) - #include "platforms/ios/QtAppDelegate-C-Interface.h" #include "platforms/ios/ios_controller.h" #endif @@ -112,7 +111,10 @@ void AmneziaApplication::init() #ifdef Q_OS_IOS IosController::Instance()->initialize(); - setImportController(m_importController.get()); + connect(IosController::Instance(), &IosController::importConfigFromOutside, m_importController.get(), + &ImportController::extractConfigFromData); + connect(IosController::Instance(), &IosController::importConfigFromOutside, m_pageController.get(), + &PageController::goToPageViewConfig); #endif m_notificationHandler.reset(NotificationHandler::create(nullptr)); diff --git a/client/platforms/ios/QtAppDelegate-C-Interface.h b/client/platforms/ios/QtAppDelegate-C-Interface.h index dd37dd2a..39adbb92 100644 --- a/client/platforms/ios/QtAppDelegate-C-Interface.h +++ b/client/platforms/ios/QtAppDelegate-C-Interface.h @@ -4,6 +4,5 @@ #include "ui/controllers/importController.h" void QtAppDelegateInitialize(); -void setImportController(ImportController*); #endif // QTAPPDELEGATECINTERFACE_H diff --git a/client/platforms/ios/QtAppDelegate.h b/client/platforms/ios/QtAppDelegate.h index a32f1b64..cd8bae27 100644 --- a/client/platforms/ios/QtAppDelegate.h +++ b/client/platforms/ios/QtAppDelegate.h @@ -1,9 +1,6 @@ #import -#import "QtAppDelegate-C-Interface.h" #include "ui/controllers/importController.h" @interface QtAppDelegate : UIResponder -+(QtAppDelegate *)sharedQtAppDelegate; -@property (nonatomic) ImportController* ImportController; @end diff --git a/client/platforms/ios/QtAppDelegate.mm b/client/platforms/ios/QtAppDelegate.mm index 432fc172..7528f2ff 100644 --- a/client/platforms/ios/QtAppDelegate.mm +++ b/client/platforms/ios/QtAppDelegate.mm @@ -1,4 +1,5 @@ #import "QtAppDelegate.h" +#import "ios_controller.h" #include @@ -79,7 +80,7 @@ bool isOpenFile = file.open(QIODevice::ReadOnly); QByteArray data = file.readAll(); - [QtAppDelegate sharedQtAppDelegate].ImportController->extractConfigFromData(QString(data)); + IosController::Instance()->importConfigFromOutside(QString(data)); return YES; } return NO; @@ -92,8 +93,4 @@ void QtAppDelegateInitialize() NSLog(@"Created a new AppDelegate"); } -void setImportController(ImportController* controller) { - [QtAppDelegate sharedQtAppDelegate].ImportController = controller; -} - @end diff --git a/client/platforms/ios/ios_controller.h b/client/platforms/ios/ios_controller.h index 4d1122b2..d6a12529 100644 --- a/client/platforms/ios/ios_controller.h +++ b/client/platforms/ios/ios_controller.h @@ -49,6 +49,7 @@ public: signals: void connectionStateChanged(Vpn::ConnectionState state); void bytesChanged(quint64 receivedBytes, quint64 sentBytes); + void importConfigFromOutside(const QString); protected slots: From a96f485e3c8d683323dfc889f64e0f2a648eec06 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 1 Sep 2023 17:39:23 +0500 Subject: [PATCH 088/278] added display of all protocols in the settings after installing a new container - set the app's max height and width to 600/800 - expanded the description when removing containers --- CMakeLists.txt | 2 +- client/amnezia_application.cpp | 2 +- client/ui/controllers/installController.cpp | 31 +++++++++++-------- .../Pages2/PageProtocolOpenVpnSettings.qml | 1 + client/ui/qml/Pages2/PageProtocolRaw.qml | 1 + .../qml/Pages2/PageSettingsServerProtocol.qml | 1 + client/ui/qml/main2.qml | 2 ++ 7 files changed, 25 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index aec2a974..875d664d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.0.3.1 +project(${PROJECT} VERSION 4.0.4.1 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index d14917f0..ebd763dd 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -206,8 +206,8 @@ void AmneziaApplication::loadFonts() void AmneziaApplication::loadTranslator() { auto locale = m_settings->getAppLanguage(); + m_translator.reset(new QTranslator()); if (locale != QLocale::English) { - m_translator.reset(new QTranslator()); if (m_translator->load(locale, QString("amneziavpn"), QLatin1String("_"), QLatin1String(":/i18n"))) { if (QCoreApplication::installTranslator(m_translator.get())) { m_settings->setAppLanguage(locale); diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 0a3da475..3c0752cc 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -59,20 +59,25 @@ InstallController::~InstallController() void InstallController::install(DockerContainer container, int port, TransportProto transportProto) { - Proto mainProto = ContainerProps::defaultProtocol(container); - QJsonObject containerConfig; - - containerConfig.insert(config_key::port, QString::number(port)); - containerConfig.insert(config_key::transport_proto, ProtocolProps::transportProtoToString(transportProto, mainProto)); - - if (container == DockerContainer::Sftp) { - containerConfig.insert(config_key::userName, protocols::sftp::defaultUserName); - containerConfig.insert(config_key::password, Utils::getRandomString(10)); - } - QJsonObject config; - config.insert(config_key::container, ContainerProps::containerToString(container)); - config.insert(ProtocolProps::protoToString(mainProto), containerConfig); + auto mainProto = ContainerProps::defaultProtocol(container); + for (auto protocol : ContainerProps::protocolsForContainer(container)) { + QJsonObject containerConfig; + + if (protocol == mainProto) { + containerConfig.insert(config_key::port, QString::number(port)); + containerConfig.insert(config_key::transport_proto, + ProtocolProps::transportProtoToString(transportProto, protocol)); + + if (container == DockerContainer::Sftp) { + containerConfig.insert(config_key::userName, protocols::sftp::defaultUserName); + containerConfig.insert(config_key::password, Utils::getRandomString(10)); + } + + config.insert(config_key::container, ContainerProps::containerToString(container)); + } + config.insert(ProtocolProps::protoToString(protocol), containerConfig); + } if (m_shouldCreateServer) { if (isServerAlreadyExists()) { diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index 659193ca..607b6584 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -362,6 +362,7 @@ PageType { onClicked: { questionDrawer.headerText = qsTr("Remove OpenVpn from server?") + questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it") questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.noButtonText = qsTr("Cancel") diff --git a/client/ui/qml/Pages2/PageProtocolRaw.qml b/client/ui/qml/Pages2/PageProtocolRaw.qml index 5c70b1c0..4c155d61 100644 --- a/client/ui/qml/Pages2/PageProtocolRaw.qml +++ b/client/ui/qml/Pages2/PageProtocolRaw.qml @@ -174,6 +174,7 @@ PageType { clickedFunction: function() { questionDrawer.headerText = qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() + qsTr(" from server?") + questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it") questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.noButtonText = qsTr("Cancel") diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml index f1f067c2..2163a71c 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -114,6 +114,7 @@ PageType { clickedFunction: function() { questionDrawer.headerText = qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() + qsTr(" from server?") + questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it") questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.noButtonText = qsTr("Cancel") diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index d3c3aa1b..e78f35a2 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -15,6 +15,8 @@ Window { height: GC.screenHeight minimumWidth: GC.isDesktop() ? 360 : 0 minimumHeight: GC.isDesktop() ? 640 : 0 + maximumWidth: 600 + maximumHeight: 800 color: "#0E0E11" From 195a3ab170eb23e749cf1edf7f257d6867371c97 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Fri, 1 Sep 2023 17:29:48 +0300 Subject: [PATCH 089/278] Save files for iOS --- client/fileUtilites.cpp | 5 +++-- client/platforms/ios/QtAppDelegate-C-Interface.h | 2 -- client/platforms/ios/QtAppDelegate.h | 2 -- client/ui/qml/Components/ShareConnectionDrawer.qml | 8 +++++++- client/ui/qml/Pages2/PageSettingsLogging.qml | 8 +++++++- client/ui/qml/Pages2/PageSettingsSplitTunneling.qml | 6 +++++- 6 files changed, 22 insertions(+), 9 deletions(-) diff --git a/client/fileUtilites.cpp b/client/fileUtilites.cpp index 9cbc5a10..301344a7 100644 --- a/client/fileUtilites.cpp +++ b/client/fileUtilites.cpp @@ -24,7 +24,8 @@ void FileUtilites::saveFile(QString fileName, const QString &data) #endif #ifdef Q_OS_IOS - QFile file(fileName); + QUrl fileUrl = QDir::tempPath() + "/" + fileName; + QFile file(fileUrl.toString()); #else QUrl fileUrl = QUrl(fileName); QFile file(fileUrl.toLocalFile()); @@ -37,7 +38,7 @@ void FileUtilites::saveFile(QString fileName, const QString &data) #ifdef Q_OS_IOS QStringList filesToSend; - filesToSend.append(fileName); + filesToSend.append(fileUrl.toString()); MobileUtils::shareText(filesToSend); return; #else diff --git a/client/platforms/ios/QtAppDelegate-C-Interface.h b/client/platforms/ios/QtAppDelegate-C-Interface.h index 39adbb92..dd358097 100644 --- a/client/platforms/ios/QtAppDelegate-C-Interface.h +++ b/client/platforms/ios/QtAppDelegate-C-Interface.h @@ -1,8 +1,6 @@ #ifndef QTAPPDELEGATECINTERFACE_H #define QTAPPDELEGATECINTERFACE_H -#include "ui/controllers/importController.h" - void QtAppDelegateInitialize(); #endif // QTAPPDELEGATECINTERFACE_H diff --git a/client/platforms/ios/QtAppDelegate.h b/client/platforms/ios/QtAppDelegate.h index cd8bae27..5148c2ca 100644 --- a/client/platforms/ios/QtAppDelegate.h +++ b/client/platforms/ios/QtAppDelegate.h @@ -1,6 +1,4 @@ #import -#include "ui/controllers/importController.h" - @interface QtAppDelegate : UIResponder @end diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 41dd2ab2..8f4498a9 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -69,7 +69,13 @@ DrawerType { text: qsTr("Share") imageSource: "qrc:/images/controls/share-2.svg" - onClicked: fileDialog.open() + onClicked: { + if (Qt.platform.os === "ios") { + ExportController.saveFile("amnezia_config.vpn") + } else { + fileDialog.open() + } + } FileDialog { id: fileDialog diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml index cfe7d7c9..0e4e486e 100644 --- a/client/ui/qml/Pages2/PageSettingsLogging.qml +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -100,7 +100,13 @@ PageType { image: "qrc:/images/controls/save.svg" - onClicked: fileDialog.open() + onClicked: { + if (Qt.platform.os === "ios") { + SettingsController.exportLogsFile("AmneziaVPN.log") + } else { + fileDialog.open() + } + } FileDialog { id: fileDialog diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index e074b4ce..898ce17b 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -302,7 +302,11 @@ PageType { text: qsTr("Save site list") clickedFunction: function() { - saveFileDialog.open() + if (Qt.platform.os === "ios") { + ExportController.saveFile("amezia_tunnel.json") + } else { + saveFileDialog.open() + } } FileDialog { From c4f94efe249146f675b2156745c74de3053e2d26 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Sat, 2 Sep 2023 17:04:35 -0400 Subject: [PATCH 090/278] Android fileSave fixes --- client/ui/qml/Components/ShareConnectionDrawer.qml | 6 ++++-- client/ui/qml/Pages2/PageSettingsLogging.qml | 2 +- client/ui/qml/Pages2/PageSettingsSplitTunneling.qml | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 8f4498a9..4971f93a 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -13,6 +13,8 @@ import ContainerProps 1.0 import "./" import "../Controls2" import "../Controls2/TextTypes" +import "../Config" +import "../Components" DrawerType { id: root @@ -70,8 +72,8 @@ DrawerType { imageSource: "qrc:/images/controls/share-2.svg" onClicked: { - if (Qt.platform.os === "ios") { - ExportController.saveFile("amnezia_config.vpn") + if (GC.isMobile()) { + ExportController.saveFile(configFileName) } else { fileDialog.open() } diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml index 0e4e486e..bab36c4c 100644 --- a/client/ui/qml/Pages2/PageSettingsLogging.qml +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -101,7 +101,7 @@ PageType { image: "qrc:/images/controls/save.svg" onClicked: { - if (Qt.platform.os === "ios") { + if (GC.isMobile()) { SettingsController.exportLogsFile("AmneziaVPN.log") } else { fileDialog.open() diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index 898ce17b..0c2ebdfa 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -302,7 +302,7 @@ PageType { text: qsTr("Save site list") clickedFunction: function() { - if (Qt.platform.os === "ios") { + if (GC.isMobile()) { ExportController.saveFile("amezia_tunnel.json") } else { saveFileDialog.open() From 7eaaef6e7509a09d9575ebc08b05ae4f3abc1a17 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Sun, 3 Sep 2023 23:18:08 +0300 Subject: [PATCH 091/278] Connection button status change for background start --- client/platforms/ios/ios_controller.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/client/platforms/ios/ios_controller.mm b/client/platforms/ios/ios_controller.mm index 6f23ac81..af0d7100 100644 --- a/client/platforms/ios/ios_controller.mm +++ b/client/platforms/ios/ios_controller.mm @@ -98,6 +98,7 @@ bool IosController::initialize() if (manager.connection.status == NEVPNStatusConnected) { m_currentTunnel = manager; qDebug() << "IosController::initialize : VPN already connected"; + emit connectionStateChanged(Vpn::ConnectionState::Connected); break; // TODO: show connected state From 4ab006f065c63bf02a86c6caafd5c92ad7d6162c Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 6 Sep 2023 13:37:37 +0500 Subject: [PATCH 092/278] added swipe up for menu on PageHome --- client/ui/controllers/pageController.h | 2 ++ .../ConnectionTypeSelectionDrawer.qml | 4 +-- .../qml/Components/HomeContainersListView.qml | 2 +- .../Components/SettingsContainersListView.qml | 16 +++++----- client/ui/qml/Controls2/BackButtonType.qml | 2 +- client/ui/qml/Controls2/DrawerType.qml | 1 + client/ui/qml/Controls2/PageType.qml | 32 ++++--------------- client/ui/qml/Pages2/PageHome.qml | 11 +++++-- .../Pages2/PageProtocolOpenVpnSettings.qml | 2 +- client/ui/qml/Pages2/PageProtocolRaw.qml | 2 +- .../ui/qml/Pages2/PageServiceSftpSettings.qml | 2 +- .../Pages2/PageServiceTorWebsiteSettings.qml | 2 +- client/ui/qml/Pages2/PageSettings.qml | 10 +++--- .../ui/qml/Pages2/PageSettingsApplication.qml | 2 +- .../ui/qml/Pages2/PageSettingsConnection.qml | 4 +-- .../ui/qml/Pages2/PageSettingsServerData.qml | 12 +++---- .../qml/Pages2/PageSettingsServerProtocol.qml | 4 +-- .../ui/qml/Pages2/PageSettingsServersList.qml | 2 +- .../Pages2/PageSetupWizardConfigSource.qml | 10 +++--- .../qml/Pages2/PageSetupWizardCredentials.qml | 2 +- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 6 ++-- .../qml/Pages2/PageSetupWizardInstalling.qml | 16 +++++----- .../PageSetupWizardProtocolSettings.qml | 2 +- .../qml/Pages2/PageSetupWizardProtocols.qml | 2 +- client/ui/qml/Pages2/PageSetupWizardStart.qml | 2 +- .../ui/qml/Pages2/PageSetupWizardTextKey.qml | 2 +- .../qml/Pages2/PageSetupWizardViewConfig.qml | 6 ++-- client/ui/qml/Pages2/PageStart.qml | 15 +++++++++ 28 files changed, 90 insertions(+), 85 deletions(-) diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 1dd16090..8d3da507 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -79,6 +79,8 @@ public slots: void showOnStartup(); signals: + void goToPage(PageLoader::PageEnum page, bool slide = true); + void goToStartPage(); void goToPageHome(); void goToPageSettings(); void goToPageViewConfig(); diff --git a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml index 8b377600..ecde1554 100644 --- a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml +++ b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml @@ -41,7 +41,7 @@ DrawerType { rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { - goToPage(PageEnum.PageSetupWizardCredentials) + PageController.goToPage(PageEnum.PageSetupWizardCredentials) root.visible = false } } @@ -55,7 +55,7 @@ DrawerType { rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { - goToPage(PageEnum.PageSetupWizardConfigSource) + PageController.goToPage(PageEnum.PageSetupWizardConfigSource) root.visible = false } } diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index fc4dd8b3..f5d27c00 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -67,7 +67,7 @@ ListView { } else { ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(index)) InstallController.setShouldCreateServer(false) - goToPage(PageEnum.PageSetupWizardProtocolSettings) + PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) containersDropDown.menuVisible = false menu.visible = false } diff --git a/client/ui/qml/Components/SettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml index 2489323b..a0c74a04 100644 --- a/client/ui/qml/Components/SettingsContainersListView.qml +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -45,44 +45,44 @@ ListView { if (config[ContainerProps.containerTypeToString(containerIndex)]["isThirdPartyConfig"]) { ProtocolsModel.updateModel(config) - goToPage(PageEnum.PageProtocolRaw) + PageController.goToPage(PageEnum.PageProtocolRaw) return } switch (containerIndex) { case ContainerEnum.OpenVpn: { OpenVpnConfigModel.updateModel(config) - goToPage(PageEnum.PageProtocolOpenVpnSettings) + PageController.goToPage(PageEnum.PageProtocolOpenVpnSettings) break } case ContainerEnum.WireGuard: { ProtocolsModel.updateModel(config) - goToPage(PageEnum.PageProtocolRaw) + PageController.goToPage(PageEnum.PageProtocolRaw) // WireGuardConfigModel.updateModel(config) // goToPage(PageEnum.PageProtocolWireGuardSettings) break } case ContainerEnum.Ipsec: { ProtocolsModel.updateModel(config) - goToPage(PageEnum.PageProtocolRaw) + PageController.goToPage(PageEnum.PageProtocolRaw) // Ikev2ConfigModel.updateModel(config) // goToPage(PageEnum.PageProtocolIKev2Settings) break } case ContainerEnum.Sftp: { SftpConfigModel.updateModel(config) - goToPage(PageEnum.PageServiceSftpSettings) + PageController.goToPage(PageEnum.PageServiceSftpSettings) break } case ContainerEnum.TorWebSite: { - goToPage(PageEnum.PageServiceTorWebsiteSettings) + PageController.goToPage(PageEnum.PageServiceTorWebsiteSettings) break } default: { if (serviceType !== ProtocolEnum.Other) { //todo disable settings for dns container ProtocolsModel.updateModel(config) - goToPage(PageEnum.PageSettingsServerProtocol) + PageController.goToPage(PageEnum.PageSettingsServerProtocol) } } } @@ -90,7 +90,7 @@ ListView { } else { ContainersModel.setCurrentlyProcessedContainerIndex(root.model.mapToSource(index)) InstallController.setShouldCreateServer(false) - goToPage(PageEnum.PageSetupWizardProtocolSettings) + PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) } } diff --git a/client/ui/qml/Controls2/BackButtonType.qml b/client/ui/qml/Controls2/BackButtonType.qml index 91f5f28f..67ffbd9c 100644 --- a/client/ui/qml/Controls2/BackButtonType.qml +++ b/client/ui/qml/Controls2/BackButtonType.qml @@ -27,7 +27,7 @@ Item { if (backButtonFunction && typeof backButtonFunction === "function") { backButtonFunction() } else { - closePage() + PageController.closePage() } } } diff --git a/client/ui/qml/Controls2/DrawerType.qml b/client/ui/qml/Controls2/DrawerType.qml index 97fbf034..35d03449 100644 --- a/client/ui/qml/Controls2/DrawerType.qml +++ b/client/ui/qml/Controls2/DrawerType.qml @@ -6,6 +6,7 @@ Drawer { clip: true modal: true + dragMargin: -10 enter: Transition { SmoothedAnimation { diff --git a/client/ui/qml/Controls2/PageType.qml b/client/ui/qml/Controls2/PageType.qml index cb6fa142..193fbcf2 100644 --- a/client/ui/qml/Controls2/PageType.qml +++ b/client/ui/qml/Controls2/PageType.qml @@ -7,35 +7,15 @@ Item { property StackView stackView: StackView.view - function goToPage(page, slide = true) { - var pagePath = PageController.getPagePath(page) - if (slide) { - root.stackView.push(pagePath, { "objectName" : pagePath }, StackView.PushTransition) - } else { - root.stackView.push(pagePath, { "objectName" : pagePath }, StackView.Immediate) - } - } - - function closePage() { - if (root.stackView.depth <= 1) { - return - } - root.stackView.pop() - } - - function goToStartPage() { - while (root.stackView.depth > 1) { - root.stackView.pop() - } - } - MouseArea { - z: -1 + z: 99 anchors.fill: parent - onClicked: { - console.log("base mouse area pressed") - focus = true + enabled: true + + onPressed: function(mouse) { + forceActiveFocus() + mouse.accepted = false } } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index d40f8748..0c778215 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -26,11 +26,15 @@ PageType { property string defaultServerHostName: ServersModel.defaultServerHostName property string defaultContainerName: ContainersModel.defaultContainerName - ConnectButton { + Item { anchors.top: parent.top anchors.bottom: buttonBackground.top anchors.right: parent.right anchors.left: parent.left + + ConnectButton { + anchors.centerIn: parent + } } Connections { @@ -125,6 +129,9 @@ PageType { DrawerType { id: menu + interactive: true + dragMargin: buttonBackground.height + 56 // page start tabBar height + width: parent.width height: parent.height * 0.9 @@ -320,7 +327,7 @@ PageType { onClicked: function() { ServersModel.currentlyProcessedIndex = index - goToPage(PageEnum.PageSettingsServerInfo) + PageController.goToPage(PageEnum.PageSettingsServerInfo) menu.visible = false } } diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index 607b6584..aed1dbc1 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -368,7 +368,7 @@ PageType { questionDrawer.yesButtonFunction = function() { questionDrawer.visible = false - goToPage(PageEnum.PageDeinstalling) + PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeCurrentlyProcessedContainer() } questionDrawer.noButtonFunction = function() { diff --git a/client/ui/qml/Pages2/PageProtocolRaw.qml b/client/ui/qml/Pages2/PageProtocolRaw.qml index 4c155d61..8bbfab14 100644 --- a/client/ui/qml/Pages2/PageProtocolRaw.qml +++ b/client/ui/qml/Pages2/PageProtocolRaw.qml @@ -180,7 +180,7 @@ PageType { questionDrawer.yesButtonFunction = function() { questionDrawer.visible = false - goToPage(PageEnum.PageDeinstalling) + PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeCurrentlyProcessedContainer() } questionDrawer.noButtonFunction = function() { diff --git a/client/ui/qml/Pages2/PageServiceSftpSettings.qml b/client/ui/qml/Pages2/PageServiceSftpSettings.qml index 5eb288f8..fead034b 100644 --- a/client/ui/qml/Pages2/PageServiceSftpSettings.qml +++ b/client/ui/qml/Pages2/PageServiceSftpSettings.qml @@ -259,7 +259,7 @@ PageType { questionDrawer.yesButtonFunction = function() { questionDrawer.visible = false - goToPage(PageEnum.PageDeinstalling) + PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeCurrentlyProcessedContainer() } questionDrawer.noButtonFunction = function() { diff --git a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml index 38490375..04d7076c 100644 --- a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml +++ b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml @@ -144,7 +144,7 @@ PageType { questionDrawer.yesButtonFunction = function() { questionDrawer.visible = false - goToPage(PageEnum.PageDeinstalling) + PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeCurrentlyProcessedContainer() } questionDrawer.noButtonFunction = function() { diff --git a/client/ui/qml/Pages2/PageSettings.qml b/client/ui/qml/Pages2/PageSettings.qml index 6c088a70..e020dc2c 100644 --- a/client/ui/qml/Pages2/PageSettings.qml +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -46,7 +46,7 @@ PageType { leftImageSource: "qrc:/images/controls/server.svg" clickedFunction: function() { - goToPage(PageEnum.PageSettingsServersList) + PageController.goToPage(PageEnum.PageSettingsServersList) } } @@ -60,7 +60,7 @@ PageType { leftImageSource: "qrc:/images/controls/radio.svg" clickedFunction: function() { - goToPage(PageEnum.PageSettingsConnection) + PageController.goToPage(PageEnum.PageSettingsConnection) } } @@ -74,7 +74,7 @@ PageType { leftImageSource: "qrc:/images/controls/app.svg" clickedFunction: function() { - goToPage(PageEnum.PageSettingsApplication) + PageController.goToPage(PageEnum.PageSettingsApplication) } } @@ -88,7 +88,7 @@ PageType { leftImageSource: "qrc:/images/controls/save.svg" clickedFunction: function() { - goToPage(PageEnum.PageSettingsBackup) + PageController.goToPage(PageEnum.PageSettingsBackup) } } @@ -102,7 +102,7 @@ PageType { leftImageSource: "qrc:/images/controls/amnezia.svg" clickedFunction: function() { - goToPage(PageEnum.PageSettingsAbout) + PageController.goToPage(PageEnum.PageSettingsAbout) } } diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index 7b628037..6f5e48a2 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -112,7 +112,7 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { - goToPage(PageEnum.PageSettingsLogging) + PageController.goToPage(PageEnum.PageSettingsLogging) } } diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml index 0692abda..78d4a681 100644 --- a/client/ui/qml/Pages2/PageSettingsConnection.qml +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -86,7 +86,7 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { - goToPage(PageEnum.PageSettingsDns) + PageController.goToPage(PageEnum.PageSettingsDns) } } @@ -100,7 +100,7 @@ PageType { rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { - goToPage(PageEnum.PageSettingsSplitTunneling) + PageController.goToPage(PageEnum.PageSettingsSplitTunneling) } } diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 8ae90351..3eb07ce9 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -32,20 +32,20 @@ PageType { if (!ServersModel.getServersCount()) { PageController.replaceStartPage() } else { - goToStartPage() - goToPage(PageEnum.PageSettingsServersList) + PageController.goToStartPage() + PageController.goToPage(PageEnum.PageSettingsServersList) } PageController.showNotificationMessage(finishedMessage) } function onRemoveAllContainersFinished(finishedMessage) { - closePage() // close deInstalling page + PageController.closePage() // close deInstalling page PageController.showNotificationMessage(finishedMessage) } function onRemoveCurrentlyProcessedContainerFinished(finishedMessage) { - closePage() // close deInstalling page - closePage() // close page with remove button + PageController.closePage() // close deInstalling page + PageController.closePage() // close page with remove button PageController.showNotificationMessage(finishedMessage) } } @@ -173,7 +173,7 @@ PageType { questionDrawer.yesButtonFunction = function() { questionDrawer.visible = false - goToPage(PageEnum.PageDeinstalling) + PageController.goToPage(PageEnum.PageDeinstalling) if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { ConnectionController.closeConnection() } diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml index 2163a71c..14d34590 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -87,7 +87,7 @@ PageType { case ProtocolEnum.WireGuard: WireGuardConfigModel.updateModel(ProtocolsModel.getConfig()); break; case ProtocolEnum.Ipsec: Ikev2ConfigModel.updateModel(ProtocolsModel.getConfig()); break; } - goToPage(protocolPage); + PageController.goToPage(protocolPage); } MouseArea { @@ -120,7 +120,7 @@ PageType { questionDrawer.yesButtonFunction = function() { questionDrawer.visible = false - goToPage(PageEnum.PageDeinstalling) + PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeCurrentlyProcessedContainer() } questionDrawer.noButtonFunction = function() { diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index 40e51e9e..c0807f35 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -98,7 +98,7 @@ PageType { clickedFunction: function() { ServersModel.currentlyProcessedIndex = index - goToPage(PageEnum.PageSettingsServerInfo) + PageController.goToPage(PageEnum.PageSettingsServerInfo) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 6362ca6d..c0ad6249 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -17,8 +17,8 @@ PageType { target: ImportController function onQrDecodingFinished() { - closePage() - goToPage(PageEnum.PageSetupWizardViewConfig) + PageController.closePage() + PageController.goToPage(PageEnum.PageSetupWizardViewConfig) } } @@ -84,7 +84,7 @@ It's okay as long as it's from someone you trust.") PageController.showBusyIndicator(false) } else { ImportController.extractConfigFromFile(fileDialog.selectedFile.toString()) - goToPage(PageEnum.PageSetupWizardViewConfig) + PageController.goToPage(PageEnum.PageSetupWizardViewConfig) } } } @@ -103,7 +103,7 @@ It's okay as long as it's from someone you trust.") clickedFunction: function() { ImportController.startDecodingQr() if (Qt.platform.os === "ios") { - goToPage(PageEnum.PageSetupWizardQrReader) + PageController.goToPage(PageEnum.PageSetupWizardQrReader) } } } @@ -120,7 +120,7 @@ It's okay as long as it's from someone you trust.") leftImageSource: "qrc:/images/controls/text-cursor.svg" clickedFunction: function() { - goToPage(PageEnum.PageSetupWizardTextKey) + PageController.goToPage(PageEnum.PageSetupWizardTextKey) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index d089a70d..bc24c196 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -104,7 +104,7 @@ PageType { return } - goToPage(PageEnum.PageSetupWizardEasy) + PageController.goToPage(PageEnum.PageSetupWizardEasy) } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index 375a8332..6fdc4b3e 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -159,12 +159,12 @@ PageType { onClicked: function() { if (root.isEasySetup) { ContainersModel.setCurrentlyProcessedContainerIndex(containers.dockerContainer) - goToPage(PageEnum.PageSetupWizardInstalling) + PageController.goToPage(PageEnum.PageSetupWizardInstalling) InstallController.install(containers.dockerContainer, containers.containerDefaultPort, containers.containerDefaultTransportProto) } else { - goToPage(PageEnum.PageSetupWizardProtocols) + PageController.goToPage(PageEnum.PageSetupWizardProtocols) } } } @@ -186,7 +186,7 @@ PageType { text: qsTr("Set up later") onClicked: function() { - goToPage(PageEnum.PageSetupWizardInstalling) + PageController.goToPage(PageEnum.PageSetupWizardInstalling) InstallController.addEmptyServer() } } diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index 20c589ee..f2919398 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -24,28 +24,28 @@ PageType { target: InstallController function onInstallContainerFinished(finishedMessage, isServiceInstall) { - goToStartPage() + PageController.goToStartPage() if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) { PageController.restorePageHomeState(true) } else if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSettings)) { - goToPage(PageEnum.PageSettingsServersList, false) - goToPage(PageEnum.PageSettingsServerInfo, false) + PageController.goToPage(PageEnum.PageSettingsServersList, false) + PageController.goToPage(PageEnum.PageSettingsServerInfo, false) if (isServiceInstall) { PageController.goToPageSettingsServerServices() } } else { - goToPage(PageEnum.PageHome) + PageController.goToPage(PageEnum.PageHome) } PageController.showNotificationMessage(finishedMessage) } function onInstallServerFinished(finishedMessage) { - goToStartPage() + PageController.goToStartPage() if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) { PageController.restorePageHomeState() } else if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSettings)) { - goToPage(PageEnum.PageSettingsServersList, false) + PageController.goToPage(PageEnum.PageSettingsServersList, false) } else { PageController.replaceStartPage() } @@ -54,9 +54,9 @@ PageType { } function onServerAlreadyExists(serverIndex) { - goToStartPage() + PageController.goToStartPage() ServersModel.currentlyProcessedIndex = serverIndex - goToPage(PageEnum.PageSettingsServerInfo, false) + PageController.goToPage(PageEnum.PageSettingsServerInfo, false) PageController.showErrorMessage(qsTr("The server has already been added to the application")) } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index d64fa097..6552d873 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -229,7 +229,7 @@ PageType { text: qsTr("Install") onClicked: function() { - goToPage(PageEnum.PageSetupWizardInstalling); + PageController.goToPage(PageEnum.PageSetupWizardInstalling); InstallController.install(dockerContainer, port.textFieldText, transportProtoSelector.currentIndex) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index 8c7f5de0..71b33eb0 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -105,7 +105,7 @@ PageType { clickedFunction: function() { ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(index)) - goToPage(PageEnum.PageSetupWizardProtocolSettings) + PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) } } diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index 3bc57495..2ce93e53 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -17,7 +17,7 @@ PageType { target: PageController function onGoToPageViewConfig() { - goToPage(PageEnum.PageSetupWizardViewConfig) + PageController.goToPage(PageEnum.PageSetupWizardViewConfig) } function onShowBusyIndicator(visible) { diff --git a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml index 504645d1..4cdfc444 100644 --- a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml +++ b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml @@ -72,7 +72,7 @@ PageType { onClicked: function() { ImportController.extractConfigFromCode(textKey.textFieldText) - goToPage(PageEnum.PageSetupWizardViewConfig) + PageController.goToPage(PageEnum.PageSetupWizardViewConfig) } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml index 222fbd11..2f1fc392 100644 --- a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml +++ b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml @@ -19,16 +19,16 @@ PageType { target: ImportController function onImportErrorOccurred(errorMessage) { - closePage() + PageController.closePage() PageController.showErrorMessage(errorMessage) } function onImportFinished() { - goToStartPage() + PageController.goToStartPage() if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) { PageController.restorePageHomeState() } else if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSettings)) { - goToPage(PageEnum.PageSettingsServersList, false) + PageController.goToPage(PageEnum.PageSettingsServersList, false) } else { PageController.replaceStartPage() } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index e4cc02ee..31474c12 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -47,6 +47,21 @@ PageType { } tabBarStackView.pop() } + + function onGoToPage(page, slide) { + var pagePath = PageController.getPagePath(page) + if (slide) { + tabBarStackView.push(pagePath, { "objectName" : pagePath }, StackView.PushTransition) + } else { + tabBarStackView.push(pagePath, { "objectName" : pagePath }, StackView.Immediate) + } + } + + function onGoToStartPage() { + while (tabBarStackView.depth > 1) { + tabBarStackView.pop() + } + } } Connections { From c1663278355db5cb508d64b7f5b252653eddf5a8 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 6 Sep 2023 22:20:59 +0500 Subject: [PATCH 093/278] filedialog for qml moved to main.qml --- client/amnezia_application.cpp | 6 + client/amnezia_application.h | 2 + client/ui/controllers/importController.cpp | 7 +- client/ui/controllers/pageController.h | 4 + client/ui/controllers/systemController.cpp | 103 ++++++++++++++++++ client/ui/controllers/systemController.h | 30 +++++ client/ui/qml/Pages2/PageSettingsBackup.qml | 14 +-- client/ui/qml/Pages2/PageSettingsLogging.qml | 2 +- .../qml/Pages2/PageSettingsSplitTunneling.qml | 28 ++--- .../Pages2/PageSetupWizardConfigSource.qml | 21 +--- client/ui/qml/main2.qml | 48 ++++++++ 11 files changed, 213 insertions(+), 52 deletions(-) create mode 100644 client/ui/controllers/systemController.cpp create mode 100644 client/ui/controllers/systemController.h diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index f248c34f..8db6f8de 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -10,6 +10,8 @@ #include #include +#include + #include "logger.h" #include "version.h" @@ -130,6 +132,7 @@ void AmneziaApplication::init() &ConnectionController::closeConnection); m_engine->load(url); + m_systemController->setQmlRoot(m_engine->rootObjects().value(0)); if (m_settings->isSaveLogs()) { if (!Logger::init()) { @@ -345,4 +348,7 @@ void AmneziaApplication::initControllers() m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel)); m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get()); + + m_systemController.reset(new SystemController(m_settings)); + m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get()); } diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 40ea81b4..1ace6d8b 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -23,6 +23,7 @@ #include "ui/controllers/pageController.h" #include "ui/controllers/settingsController.h" #include "ui/controllers/sitesController.h" +#include "ui/controllers/systemController.h" #include "ui/models/containers_model.h" #include "ui/models/languageModel.h" #include "ui/models/protocols/cloakConfigModel.h" @@ -114,6 +115,7 @@ private: QScopedPointer m_exportController; QScopedPointer m_settingsController; QScopedPointer m_sitesController; + QScopedPointer m_systemController; }; #endif // AMNEZIA_APPLICATION_H diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index b76302c5..c0aaeeb9 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include "core/errorstrings.h" @@ -87,7 +88,11 @@ ImportController::ImportController(const QSharedPointer &serversMo void ImportController::extractConfigFromFile(const QString &fileName) { - QFile file(FileUtilites::getFileName(fileName)); + QQuickItem *obj = findChild("saveFileDialog"); + + QUrl url(fileName); + QString path = url.toLocalFile(); + QFile file(path); if (file.open(QIODevice::ReadOnly)) { QString data = file.readAll(); diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 8d3da507..952f716c 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -103,6 +103,10 @@ signals: void showPassphraseRequestDrawer(); void passphraseRequestDrawerClosed(QString passphrase); + void setupFileDialogForConfig(); + void setupFileDialogForSites(bool replaceExistingSites); + void setupFileDialogForBackup(); + private: QSharedPointer m_serversModel; diff --git a/client/ui/controllers/systemController.cpp b/client/ui/controllers/systemController.cpp new file mode 100644 index 00000000..a843b660 --- /dev/null +++ b/client/ui/controllers/systemController.cpp @@ -0,0 +1,103 @@ +#include "systemController.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef Q_OS_ANDROID + #include "platforms/android/android_controller.h" +#endif + +#ifdef Q_OS_IOS + #include "platforms/ios/MobileUtils.h" + #include +#endif + +SystemController::SystemController(const std::shared_ptr &settings, QObject *parent) + : QObject(parent), m_settings(settings) +{ +} + +void SystemController::saveFile(QString fileName, const QString &data) +{ +#if defined Q_OS_ANDROID + AndroidController::instance()->shareConfig(data, fileName); + return; +#endif + +#ifdef Q_OS_IOS + QUrl fileUrl = QDir::tempPath() + "/" + fileName; + QFile file(fileUrl.toString()); +#else + QUrl fileUrl = QUrl(fileName); + QFile file(fileUrl.toLocalFile()); +#endif + + // todo check if save successful + file.open(QIODevice::WriteOnly); + file.write(data.toUtf8()); + file.close(); + +#ifdef Q_OS_IOS + QStringList filesToSend; + filesToSend.append(fileUrl.toString()); + MobileUtils::shareText(filesToSend); + return; +#else + QFileInfo fi(fileUrl.toLocalFile()); + QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); +#endif +} + +QString SystemController::getFileName() +{ + auto mainFileDialog = m_qmlRoot->findChild("mainFileDialog").parent(); + if (!mainFileDialog) { + return ""; + } + QMetaObject::invokeMethod(mainFileDialog, "open", Qt::DirectConnection); + + QEventLoop wait; + QObject::connect(this, &SystemController::fileDialogAccepted, &wait, &QEventLoop::quit); + wait.exec(); + + auto fileName = mainFileDialog->property("selectedFile").toString(); + +#ifdef Q_OS_IOS + CFURLRef url = CFURLCreateWithFileSystemPath( + kCFAllocatorDefault, + CFStringCreateWithCharacters(0, reinterpret_cast(fileName.unicode()), fileName.length()), + kCFURLPOSIXPathStyle, 0); + + if (!CFURLStartAccessingSecurityScopedResource(url)) { + qDebug() << "Could not access path " << QUrl::fromLocalFile(fileName).toString(); + } + + return fileName; +#endif + +#ifdef Q_OS_ANDROID + // patch for files containing spaces etc + const QString sep { "raw%3A%2F" }; + if (fileName.startsWith("content://") && fileName.contains(sep)) { + QString contentUrl = fileName.split(sep).at(0); + QString rawUrl = fileName.split(sep).at(1); + rawUrl.replace(" ", "%20"); + fileName = contentUrl + sep + rawUrl; + } + + return fileName; +#endif + + return QUrl(fileName).toLocalFile(); +} + +void SystemController::setQmlRoot(QObject *qmlRoot) +{ + m_qmlRoot = qmlRoot; +} diff --git a/client/ui/controllers/systemController.h b/client/ui/controllers/systemController.h new file mode 100644 index 00000000..fbcc52f1 --- /dev/null +++ b/client/ui/controllers/systemController.h @@ -0,0 +1,30 @@ +#ifndef SYSTEMCONTROLLER_H +#define SYSTEMCONTROLLER_H + +#include + +#include "settings.h" + +class SystemController : public QObject +{ + Q_OBJECT +public: + explicit SystemController(const std::shared_ptr &setting, QObject *parent = nullptr); + +public slots: + void saveFile(QString fileName, const QString &data); + QString getFileName(); + + void setQmlRoot(QObject *qmlRoot); + +signals: + void fileDialogAccepted(); + void fileDialogRejected(); + +private: + std::shared_ptr m_settings; + + QObject *m_qmlRoot; +}; + +#endif // SYSTEMCONTROLLER_H diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml index 363bc66f..edd527c1 100644 --- a/client/ui/qml/Pages2/PageSettingsBackup.qml +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -125,18 +125,8 @@ PageType { text: qsTr("Restore from backup") onClicked: { - openFileDialog.open() - } - - FileDialog { - id: openFileDialog - acceptLabel: qsTr("Open backup file") - nameFilters: [ "Backup files (*.backup)" ] - onAccepted: { - PageController.showBusyIndicator(true) - SettingsController.restoreAppConfig(openFileDialog.selectedFile.toString()) - PageController.showBusyIndicator(false) - } + PageController.setupFileDialogForBackup() + SystemController.getFileName() } } } diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml index 0e4e486e..c0a35c0f 100644 --- a/client/ui/qml/Pages2/PageSettingsLogging.qml +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -117,7 +117,7 @@ PageType { currentFile: StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN" defaultSuffix: ".log" onAccepted: { - ExportController.saveFile(fileDialog.currentFile.toString()) + SettingsController.exportLogsFile(fileDialog.currentFile.toString()) } } } diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index 898ce17b..155ecc3c 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -44,8 +44,6 @@ PageType { allExceptSites ] - property bool replaceExistingSites - QtObject { id: onlyForwardSites property string name: qsTr("Only the addresses in the list must be opened via VPN") @@ -303,7 +301,7 @@ PageType { clickedFunction: function() { if (Qt.platform.os === "ios") { - ExportController.saveFile("amezia_tunnel.json") + SitesController.exportSites("amezia_tunnel.json") } else { saveFileDialog.open() } @@ -311,6 +309,7 @@ PageType { FileDialog { id: saveFileDialog + objectName: saveFileDialog acceptLabel: qsTr("Save sites") nameFilters: [ "Sites files (*.json)" ] fileMode: FileDialog.SaveFile @@ -378,8 +377,8 @@ PageType { text: qsTr("Replace site list") clickedFunction: function() { - root.replaceExistingSites = true - openFileDialog.open() + PageController.setupFileDialogForSites(true) + SystemController.getFileName() } } @@ -390,25 +389,14 @@ PageType { text: qsTr("Add imported sites to existing ones") clickedFunction: function() { - root.replaceExistingSites = false - openFileDialog.open() + PageController.setupFileDialogForSites(false) + SystemController.getFileName() + importSitesDrawer.close() + moreActionsDrawer.close() } } DividerType {} - - FileDialog { - id: openFileDialog - acceptLabel: qsTr("Open sites file") - nameFilters: [ "Sites files (*.json)" ] - onAccepted: { - PageController.showBusyIndicator(true) - SitesController.importSites(openFileDialog.selectedFile.toString(), replaceExistingSites) - importSitesDrawer.close() - moreActionsDrawer.close() - PageController.showBusyIndicator(false) - } - } } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index c0ad6249..3f460f65 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -65,28 +65,13 @@ It's okay as long as it's from someone you trust.") Layout.fillWidth: true Layout.topMargin: 16 - text: qsTr("File with connection settings or backup") + text: !ServersModel.getServersCount() ? qsTr("File with connection settings or backup") : qsTr("File with connection settings") rightImageSource: "qrc:/images/controls/chevron-right.svg" leftImageSource: "qrc:/images/controls/folder-open.svg" clickedFunction: function() { - fileDialog.open() - } - - FileDialog { - id: fileDialog - acceptLabel: qsTr("Open config file") - nameFilters: [ "Config or backup files (*.vpn *.ovpn *.conf *.backup)" ] - onAccepted: { - if (fileDialog.selectedFile.toString().indexOf(".backup") != -1) { - PageController.showBusyIndicator(true) - SettingsController.restoreAppConfig(fileDialog.selectedFile.toString()) - PageController.showBusyIndicator(false) - } else { - ImportController.extractConfigFromFile(fileDialog.selectedFile.toString()) - PageController.goToPage(PageEnum.PageSetupWizardViewConfig) - } - } + PageController.setupFileDialogForConfig() + SystemController.getFileName() } } diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index e78f35a2..e09882c0 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -2,6 +2,7 @@ import QtQuick import QtQuick.Window import QtQuick.Controls import QtQuick.Layouts +import QtQuick.Dialogs import PageEnum 1.0 @@ -10,6 +11,7 @@ import "Controls2" Window { id: root + objectName: "mainWindow" visible: true width: GC.screenWidth height: GC.screenHeight @@ -79,6 +81,42 @@ Window { function onShowPassphraseRequestDrawer() { privateKeyPassphraseDrawer.open() } + + function onSetupFileDialogForConfig() { + mainFileDialog.acceptLabel = qsTr("Open config file") + mainFileDialog.nameFilters = !ServersModel.getServersCount() ? [ "Config or backup files (*.vpn *.ovpn *.conf *.backup)" ] : + [ "Config files (*.vpn *.ovpn *.conf)" ] + mainFileDialog.acceptFunction = function() { + if (mainFileDialog.selectedFile.toString().indexOf(".backup") !== -1 && !ServersModel.getServersCount()) { + PageController.showBusyIndicator(true) + SettingsController.restoreAppConfig(mainFileDialog.selectedFile.toString()) + PageController.showBusyIndicator(false) + } else { + ImportController.extractConfigFromFile(mainFileDialog.selectedFile) + PageController.goToPage(PageEnum.PageSetupWizardViewConfig) + } + } + } + + function onSetupFileDialogForSites(replaceExistingSites) { + mainFileDialog.acceptLabel = qsTr("Open sites file") + mainFileDialog.nameFilters = [ "Sites files (*.json)" ] + mainFileDialog.acceptFunction = function() { + PageController.showBusyIndicator(true) + SitesController.importSites(mainFileDialog.selectedFile.toString(), replaceExistingSites) + PageController.showBusyIndicator(false) + } + } + + function onSetupFileDialogForBackup() { + mainFileDialog.acceptLabel = qsTr("Open backup file") + mainFileDialog.nameFilters = [ "Backup files (*.backup)" ] + mainFileDialog.acceptFunction = function() { + PageController.showBusyIndicator(true) + SettingsController.restoreAppConfig(mainFileDialog.selectedFile.toString()) + PageController.showBusyIndicator(false) + } + } } Connections { @@ -192,4 +230,14 @@ Window { } } } + + FileDialog { + id: mainFileDialog + + property var acceptFunction + + objectName: "mainFileDialog" + + onAccepted: acceptFunction() + } } From e1fa24c2519a8c62528587f6c9ec7909278a2788 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 6 Sep 2023 22:26:37 +0500 Subject: [PATCH 094/278] moved the qml filedialog opening code below the ios section --- client/ui/controllers/systemController.cpp | 24 +++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/client/ui/controllers/systemController.cpp b/client/ui/controllers/systemController.cpp index a843b660..87d04090 100644 --- a/client/ui/controllers/systemController.cpp +++ b/client/ui/controllers/systemController.cpp @@ -56,18 +56,6 @@ void SystemController::saveFile(QString fileName, const QString &data) QString SystemController::getFileName() { - auto mainFileDialog = m_qmlRoot->findChild("mainFileDialog").parent(); - if (!mainFileDialog) { - return ""; - } - QMetaObject::invokeMethod(mainFileDialog, "open", Qt::DirectConnection); - - QEventLoop wait; - QObject::connect(this, &SystemController::fileDialogAccepted, &wait, &QEventLoop::quit); - wait.exec(); - - auto fileName = mainFileDialog->property("selectedFile").toString(); - #ifdef Q_OS_IOS CFURLRef url = CFURLCreateWithFileSystemPath( kCFAllocatorDefault, @@ -81,6 +69,18 @@ QString SystemController::getFileName() return fileName; #endif + auto mainFileDialog = m_qmlRoot->findChild("mainFileDialog").parent(); + if (!mainFileDialog) { + return ""; + } + QMetaObject::invokeMethod(mainFileDialog, "open", Qt::DirectConnection); + + QEventLoop wait; + QObject::connect(this, &SystemController::fileDialogAccepted, &wait, &QEventLoop::quit); + wait.exec(); + + auto fileName = mainFileDialog->property("selectedFile").toString(); + #ifdef Q_OS_ANDROID // patch for files containing spaces etc const QString sep { "raw%3A%2F" }; From b5dd48ad7b74e9bb8dc6d97f7b7eeaf803354ffd Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 7 Sep 2023 22:45:01 +0500 Subject: [PATCH 095/278] reworking of getting the path to the file when saving/opening files --- client/fileUtilites.cpp | 79 ------------------- client/fileUtilites.h | 16 ---- client/platforms/ios/MobileUtils.h | 7 +- client/platforms/ios/MobileUtils.mm | 28 +++++++ client/ui/controllers/exportController.cpp | 6 +- client/ui/controllers/exportController.h | 4 +- client/ui/controllers/importController.cpp | 7 +- client/ui/controllers/installController.cpp | 1 - client/ui/controllers/pageController.h | 4 - client/ui/controllers/settingsController.cpp | 8 +- client/ui/controllers/settingsController.h | 2 + client/ui/controllers/sitesController.cpp | 6 +- client/ui/controllers/sitesController.h | 2 + client/ui/controllers/systemController.cpp | 36 +++++++-- client/ui/controllers/systemController.h | 9 ++- .../qml/Components/ShareConnectionDrawer.qml | 25 +++--- client/ui/qml/Pages2/PageSettingsBackup.qml | 40 +++++----- client/ui/qml/Pages2/PageSettingsLogging.qml | 25 +++--- .../qml/Pages2/PageSettingsSplitTunneling.qml | 48 ++++++----- .../Pages2/PageSetupWizardConfigSource.qml | 15 +++- client/ui/qml/main2.qml | 42 +--------- 21 files changed, 167 insertions(+), 243 deletions(-) delete mode 100644 client/fileUtilites.cpp delete mode 100644 client/fileUtilites.h diff --git a/client/fileUtilites.cpp b/client/fileUtilites.cpp deleted file mode 100644 index 301344a7..00000000 --- a/client/fileUtilites.cpp +++ /dev/null @@ -1,79 +0,0 @@ -#include "fileUtilites.h" - -#include -#include -#include -#include -#include -#include - -#ifdef Q_OS_ANDROID - #include "platforms/android/android_controller.h" -#endif - -#ifdef Q_OS_IOS - #include "platforms/ios/MobileUtils.h" - #include -#endif - -void FileUtilites::saveFile(QString fileName, const QString &data) -{ -#if defined Q_OS_ANDROID - AndroidController::instance()->shareConfig(data, fileName); - return; -#endif - -#ifdef Q_OS_IOS - QUrl fileUrl = QDir::tempPath() + "/" + fileName; - QFile file(fileUrl.toString()); -#else - QUrl fileUrl = QUrl(fileName); - QFile file(fileUrl.toLocalFile()); -#endif - - // todo check if save successful - file.open(QIODevice::WriteOnly); - file.write(data.toUtf8()); - file.close(); - -#ifdef Q_OS_IOS - QStringList filesToSend; - filesToSend.append(fileUrl.toString()); - MobileUtils::shareText(filesToSend); - return; -#else - QFileInfo fi(fileUrl.toLocalFile()); - QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); -#endif -} - -QString FileUtilites::getFileName(QString fileName) -{ -#ifdef Q_OS_IOS - CFURLRef url = CFURLCreateWithFileSystemPath( - kCFAllocatorDefault, - CFStringCreateWithCharacters(0, reinterpret_cast(fileName.unicode()), fileName.length()), - kCFURLPOSIXPathStyle, 0); - - if (!CFURLStartAccessingSecurityScopedResource(url)) { - qDebug() << "Could not access path " << QUrl::fromLocalFile(fileName).toString(); - } - - return fileName; -#endif - -#ifdef Q_OS_ANDROID - // patch for files containing spaces etc - const QString sep { "raw%3A%2F" }; - if (fileName.startsWith("content://") && fileName.contains(sep)) { - QString contentUrl = fileName.split(sep).at(0); - QString rawUrl = fileName.split(sep).at(1); - rawUrl.replace(" ", "%20"); - fileName = contentUrl + sep + rawUrl; - } - - return fileName; -#endif - - return QUrl(fileName).toLocalFile(); -} diff --git a/client/fileUtilites.h b/client/fileUtilites.h deleted file mode 100644 index 21cb03a9..00000000 --- a/client/fileUtilites.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef FILEUTILITES_H -#define FILEUTILITES_H - -#include -#include - -class FileUtilites : public QObject -{ - Q_OBJECT - -public: - static void saveFile(QString fileName, const QString &data); - static QString getFileName(QString fileName); -}; - -#endif // FILEUTILITES_H diff --git a/client/platforms/ios/MobileUtils.h b/client/platforms/ios/MobileUtils.h index a7967fdf..cad9de9e 100644 --- a/client/platforms/ios/MobileUtils.h +++ b/client/platforms/ios/MobileUtils.h @@ -4,15 +4,16 @@ #include #include -class MobileUtils : public QObject { +class MobileUtils : public QObject +{ Q_OBJECT public: MobileUtils() = delete; public slots: - static void shareText(const QStringList& filesToSend); - + static void shareText(const QStringList &filesToSend); + static void openFile(); }; #endif // MOBILEUTILS_H diff --git a/client/platforms/ios/MobileUtils.mm b/client/platforms/ios/MobileUtils.mm index a9ad52b5..63ee0364 100644 --- a/client/platforms/ios/MobileUtils.mm +++ b/client/platforms/ios/MobileUtils.mm @@ -35,3 +35,31 @@ void MobileUtils::shareText(const QStringList& filesToSend) { } } +@interface MyFilePickerDelegate : NSObject +@end + +@implementation MyFilePickerDelegate + +- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray *)urls { + for (NSURL *url in urls) { + NSString *filePath = [url path]; + + NSData *fileData = [NSData dataWithContentsOfFile:filePath]; + NSString *fileContent = [[NSString alloc] initWithData:fileData encoding:NSUTF8StringEncoding]; + NSLog(@"Содержимое файла: %@", fileContent); + } +} + +@end + +void MobileUtils::openFile() { + UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.item"] inMode:UIDocumentPickerModeOpen]; + + MyFilePickerDelegate *filePickerDelegate = [[MyFilePickerDelegate alloc] init]; + documentPicker.delegate = filePickerDelegate; + + UIViewController *qtController = getViewController(); + if (!qtController) return; + + [qtController presentViewController:documentPicker animated:YES completion:nil]; +} diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index ddc976cc..ef5cc4e3 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -11,7 +11,7 @@ #include "configurators/openvpn_configurator.h" #include "configurators/wireguard_configurator.h" #include "core/errorstrings.h" -#include "fileUtilites.h" +#include "systemController.h" #ifdef Q_OS_ANDROID #include "platforms/android/androidutils.h" #endif @@ -200,9 +200,9 @@ QList ExportController::getQrCodes() return m_qrCodes; } -void ExportController::saveFile(const QString &fileName) +void ExportController::exportConfig(const QString &fileName) { - FileUtilites::saveFile(fileName, m_config); + SystemController::saveFile(fileName, m_config); } QList ExportController::generateQrCodeImageSeries(const QByteArray &data) diff --git a/client/ui/controllers/exportController.h b/client/ui/controllers/exportController.h index b526521e..24eaa5c8 100644 --- a/client/ui/controllers/exportController.h +++ b/client/ui/controllers/exportController.h @@ -35,7 +35,7 @@ public slots: QString getConfig(); QList getQrCodes(); - void saveFile(const QString &fileName); + void exportConfig(const QString &fileName); signals: void generateConfig(int type); @@ -43,6 +43,8 @@ signals: void exportConfigChanged(); + void saveFile(const QString &fileName, const QString &data); + private: QList generateQrCodeImageSeries(const QByteArray &data); QString svgToBase64(const QString &image); diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index c0aaeeb9..d9278ece 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -14,7 +14,6 @@ #ifdef Q_OS_IOS #include #endif -#include "fileUtilites.h" namespace { @@ -88,11 +87,7 @@ ImportController::ImportController(const QSharedPointer &serversMo void ImportController::extractConfigFromFile(const QString &fileName) { - QQuickItem *obj = findChild("saveFileDialog"); - - QUrl url(fileName); - QString path = url.toLocalFile(); - QFile file(path); + QFile file(fileName); if (file.open(QIODevice::ReadOnly)) { QString data = file.readAll(); diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 3c0752cc..1db84b36 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -8,7 +8,6 @@ #include "core/errorstrings.h" #include "core/servercontroller.h" -#include "fileUtilites.h" #include "utilities.h" namespace diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 952f716c..8d3da507 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -103,10 +103,6 @@ signals: void showPassphraseRequestDrawer(); void passphraseRequestDrawerClosed(QString passphrase); - void setupFileDialogForConfig(); - void setupFileDialogForSites(bool replaceExistingSites); - void setupFileDialogForBackup(); - private: QSharedPointer m_serversModel; diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index 7c7402e0..46993f6a 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -2,8 +2,8 @@ #include -#include "fileUtilites.h" #include "logger.h" +#include "systemController.h" #include "ui/qautostart.h" #include "version.h" @@ -70,7 +70,7 @@ void SettingsController::openLogsFolder() void SettingsController::exportLogsFile(const QString &fileName) { - FileUtilites::saveFile(fileName, Logger::getLogFile()); + SystemController::saveFile(fileName, Logger::getLogFile()); } void SettingsController::clearLogs() @@ -81,12 +81,12 @@ void SettingsController::clearLogs() void SettingsController::backupAppConfig(const QString &fileName) { - FileUtilites::saveFile(fileName, m_settings->backupAppConfig()); + SystemController::saveFile(fileName, m_settings->backupAppConfig()); } void SettingsController::restoreAppConfig(const QString &fileName) { - QFile file(FileUtilites::getFileName(fileName)); + QFile file(fileName); file.open(QIODevice::ReadOnly); diff --git a/client/ui/controllers/settingsController.h b/client/ui/controllers/settingsController.h index af816d46..3d96dc03 100644 --- a/client/ui/controllers/settingsController.h +++ b/client/ui/controllers/settingsController.h @@ -63,6 +63,8 @@ signals: void changeSettingsFinished(const QString &finishedMessage); void changeSettingsErrorOccurred(const QString &errorMessage); + void saveFile(const QString &fileName, const QString &data); + private: QSharedPointer m_serversModel; QSharedPointer m_containersModel; diff --git a/client/ui/controllers/sitesController.cpp b/client/ui/controllers/sitesController.cpp index 5821b371..a27e91d0 100644 --- a/client/ui/controllers/sitesController.cpp +++ b/client/ui/controllers/sitesController.cpp @@ -4,7 +4,7 @@ #include #include -#include "fileUtilites.h" +#include "systemController.h" #include "utilities.h" SitesController::SitesController(const std::shared_ptr &settings, @@ -82,7 +82,7 @@ void SitesController::removeSite(int index) void SitesController::importSites(const QString &fileName, bool replaceExisting) { - QFile file(FileUtilites::getFileName(fileName)); + QFile file(fileName); if (!file.open(QIODevice::ReadOnly)) { emit errorOccurred(tr("Can't open file: ") + fileName); @@ -145,7 +145,7 @@ void SitesController::exportSites(const QString &fileName) QJsonDocument jsonDocument(jsonArray); QByteArray jsonData = jsonDocument.toJson(); - FileUtilites::saveFile(fileName, jsonData); + SystemController::saveFile(fileName, jsonData); emit finished(tr("Export completed")); } diff --git a/client/ui/controllers/sitesController.h b/client/ui/controllers/sitesController.h index 2171e7b4..e66478da 100644 --- a/client/ui/controllers/sitesController.h +++ b/client/ui/controllers/sitesController.h @@ -26,6 +26,8 @@ signals: void errorOccurred(const QString &errorMessage); void finished(const QString &message); + void saveFile(const QString &fileName, const QString &data); + private: std::shared_ptr m_settings; diff --git a/client/ui/controllers/systemController.cpp b/client/ui/controllers/systemController.cpp index 87d04090..a6210b69 100644 --- a/client/ui/controllers/systemController.cpp +++ b/client/ui/controllers/systemController.cpp @@ -34,8 +34,7 @@ void SystemController::saveFile(QString fileName, const QString &data) QUrl fileUrl = QDir::tempPath() + "/" + fileName; QFile file(fileUrl.toString()); #else - QUrl fileUrl = QUrl(fileName); - QFile file(fileUrl.toLocalFile()); + QFile file(fileName); #endif // todo check if save successful @@ -49,14 +48,18 @@ void SystemController::saveFile(QString fileName, const QString &data) MobileUtils::shareText(filesToSend); return; #else - QFileInfo fi(fileUrl.toLocalFile()); + QFileInfo fi(fileName); QDesktopServices::openUrl(fi.absoluteDir().absolutePath()); #endif } -QString SystemController::getFileName() +QString SystemController::getFileName(const QString &acceptLabel, const QString &nameFilter, + const QString &selectedFile, const bool isSaveMode, const QString &defaultSuffix) { + QString fileName; #ifdef Q_OS_IOS + MobileUtils::openFile(); + CFURLRef url = CFURLCreateWithFileSystemPath( kCFAllocatorDefault, CFStringCreateWithCharacters(0, reinterpret_cast(fileName.unicode()), fileName.length()), @@ -69,17 +72,34 @@ QString SystemController::getFileName() return fileName; #endif - auto mainFileDialog = m_qmlRoot->findChild("mainFileDialog").parent(); + QObject *mainFileDialog = m_qmlRoot->findChild("mainFileDialog").parent(); if (!mainFileDialog) { return ""; } - QMetaObject::invokeMethod(mainFileDialog, "open", Qt::DirectConnection); + mainFileDialog->setProperty("acceptLabel", QVariant::fromValue(acceptLabel)); + mainFileDialog->setProperty("nameFilters", QVariant::fromValue(QStringList(nameFilter))); + if (!selectedFile.isEmpty()) { + mainFileDialog->setProperty("selectedFile", QVariant::fromValue(selectedFile)); + } + mainFileDialog->setProperty("isSaveMode", QVariant::fromValue(isSaveMode)); + mainFileDialog->setProperty("defaultSuffix", QVariant::fromValue(defaultSuffix)); + QMetaObject::invokeMethod(mainFileDialog, "open"); + + bool isFileDialogAccepted = false; QEventLoop wait; - QObject::connect(this, &SystemController::fileDialogAccepted, &wait, &QEventLoop::quit); + QObject::connect(this, &SystemController::fileDialogClosed, [&wait, &isFileDialogAccepted](const bool isAccepted) { + isFileDialogAccepted = isAccepted; + wait.quit(); + }); wait.exec(); + QObject::disconnect(this, &SystemController::fileDialogClosed, nullptr, nullptr); - auto fileName = mainFileDialog->property("selectedFile").toString(); + if (!isFileDialogAccepted) { + return ""; + } + + fileName = mainFileDialog->property("selectedFile").toString(); #ifdef Q_OS_ANDROID // patch for files containing spaces etc diff --git a/client/ui/controllers/systemController.h b/client/ui/controllers/systemController.h index fbcc52f1..274df234 100644 --- a/client/ui/controllers/systemController.h +++ b/client/ui/controllers/systemController.h @@ -11,15 +11,16 @@ class SystemController : public QObject public: explicit SystemController(const std::shared_ptr &setting, QObject *parent = nullptr); + static void saveFile(QString fileName, const QString &data); + public slots: - void saveFile(QString fileName, const QString &data); - QString getFileName(); + QString getFileName(const QString &acceptLabel, const QString &nameFilter, const QString &selectedFile = "", + const bool isSaveMode = false, const QString &defaultSuffix = ""); void setQmlRoot(QObject *qmlRoot); signals: - void fileDialogAccepted(); - void fileDialogRejected(); + void fileDialogClosed(const bool isAccepted); private: std::shared_ptr m_settings; diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 4971f93a..4d719d1a 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -72,23 +72,20 @@ DrawerType { imageSource: "qrc:/images/controls/share-2.svg" onClicked: { + var fileName = "" if (GC.isMobile()) { - ExportController.saveFile(configFileName) + fileName = configFileName } else { - fileDialog.open() + fileName = SystemController.getFileName(configCaption, + qsTr("Config files (*" + configExtension + ")"), + StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/" + configFileName, + true, + configExtension) } - } - - FileDialog { - id: fileDialog - acceptLabel: configCaption - nameFilters: [ "Config files (*" + configExtension + ")" ] - fileMode: FileDialog.SaveFile - - currentFile: StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/" + configFileName - defaultSuffix: configExtension - onAccepted: { - ExportController.saveFile(fileDialog.currentFile.toString()) + if (fileName !== "") { + PageController.showBusyIndicator(true) + ExportController.exportConfig(fileName) + PageController.showBusyIndicator(false) } } } diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml index edd527c1..6a9ea58f 100644 --- a/client/ui/qml/Pages2/PageSettingsBackup.qml +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -84,31 +84,22 @@ PageType { text: qsTr("Make a backup") onClicked: { + var fileName = "" if (GC.isMobile()) { - backupAppConfig("AmneziaVPN.backup") + fileName = "AmneziaVPN.backup" } else { - saveFileDialog.open() + fileName = SystemController.getFileName(qsTr("Save backup file"), + qsTr("Backup files (*.backup)"), + StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN", + true, + ".backup") } - } - - FileDialog { - id: saveFileDialog - acceptLabel: qsTr("Save backup file") - nameFilters: [ "Backup files (*.backup)" ] - fileMode: FileDialog.SaveFile - - currentFile: StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN" - defaultSuffix: ".backup" - onAccepted: { - makeBackupButton.backupAppConfig(saveFileDialog.currentFile.toString()) + if (fileName !== "") { + PageController.showBusyIndicator(true) + SettingsController.backupAppConfig(fileName) + PageController.showBusyIndicator(false) } } - - function backupAppConfig(fileName) { - PageController.showBusyIndicator(true) - SettingsController.backupAppConfig(fileName) - PageController.showBusyIndicator(false) - } } BasicButtonType { @@ -125,8 +116,13 @@ PageType { text: qsTr("Restore from backup") onClicked: { - PageController.setupFileDialogForBackup() - SystemController.getFileName() + var fileName = SystemController.getFileName(qsTr("Open backup file"), + qsTr("Backup files (*.backup)")) + if (fileName !== "") { + PageController.showBusyIndicator(true) + SettingsController.restoreAppConfig(fileName) + PageController.showBusyIndicator(false) + } } } } diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml index d4f1a1d4..42f33901 100644 --- a/client/ui/qml/Pages2/PageSettingsLogging.qml +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -101,23 +101,20 @@ PageType { image: "qrc:/images/controls/save.svg" onClicked: { + var fileName = "" if (GC.isMobile()) { - SettingsController.exportLogsFile("AmneziaVPN.log") + fileName = "AmneziaVPN.log" } else { - fileDialog.open() + fileName = SystemController.getFileName(qsTr("Save logs"), + qsTr("Logs files (*.log)"), + StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN", + true, + ".log") } - } - - FileDialog { - id: fileDialog - acceptLabel: qsTr("Save logs") - nameFilters: [ "Logs files (*.log)" ] - fileMode: FileDialog.SaveFile - - currentFile: StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN" - defaultSuffix: ".log" - onAccepted: { - SettingsController.exportLogsFile(fileDialog.currentFile.toString()) + if (fileName !== "") { + PageController.showBusyIndicator(true) + SettingsController.exportLogsFile(fileName) + PageController.showBusyIndicator(false) } } } diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index eb06a586..b79d5d22 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -300,25 +300,19 @@ PageType { text: qsTr("Save site list") clickedFunction: function() { + var fileName = "" if (GC.isMobile()) { - SitesController.exportSites("amezia_tunnel.json") + fileName = "amnezia_sites.json" } else { - saveFileDialog.open() + fileName = SystemController.getFileName(qsTr("Save sites"), + qsTr("Sites files (*.json)"), + StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/amnezia_sites", + true, + ".json") } - } - - FileDialog { - id: saveFileDialog - objectName: saveFileDialog - acceptLabel: qsTr("Save sites") - nameFilters: [ "Sites files (*.json)" ] - fileMode: FileDialog.SaveFile - - currentFile: StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/sites" - defaultSuffix: ".json" - onAccepted: { + if (fileName !== "") { PageController.showBusyIndicator(true) - SitesController.exportSites(saveFileDialog.currentFile.toString()) + SitesController.exportSites(fileName) moreActionsDrawer.close() PageController.showBusyIndicator(false) } @@ -377,8 +371,11 @@ PageType { text: qsTr("Replace site list") clickedFunction: function() { - PageController.setupFileDialogForSites(true) - SystemController.getFileName() + var fileName = SystemController.getFileName(qsTr("Open sites file"), + qsTr("Sites files (*.json)")) + if (fileName !== "") { + importSitesDrawerContent.importSites(fileName, true) + } } } @@ -389,13 +386,22 @@ PageType { text: qsTr("Add imported sites to existing ones") clickedFunction: function() { - PageController.setupFileDialogForSites(false) - SystemController.getFileName() - importSitesDrawer.close() - moreActionsDrawer.close() + var fileName = SystemController.getFileName(qsTr("Open sites file"), + qsTr("Sites files (*.json)")) + if (fileName !== "") { + importSitesDrawerContent.importSites(fileName, false) + } } } + function importSites(fileName, replaceExistingSites) { + PageController.showBusyIndicator(true) + SitesController.importSites(fileName, replaceExistingSites) + PageController.showBusyIndicator(false) + importSitesDrawer.close() + moreActionsDrawer.close() + } + DividerType {} } } diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 3f460f65..07189eb7 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -70,8 +70,19 @@ It's okay as long as it's from someone you trust.") leftImageSource: "qrc:/images/controls/folder-open.svg" clickedFunction: function() { - PageController.setupFileDialogForConfig() - SystemController.getFileName() + var nameFilter = !ServersModel.getServersCount() ? "Config or backup files (*.vpn *.ovpn *.conf *.backup)" : + "Config files (*.vpn *.ovpn *.conf)" + var fileName = SystemController.getFileName(qsTr("Open config file"), nameFilter) + if (fileName !== "") { + if (fileName.indexOf(".backup") !== -1 && !ServersModel.getServersCount()) { + PageController.showBusyIndicator(true) + SettingsController.restoreAppConfig(fileName) + PageController.showBusyIndicator(false) + } else { + ImportController.extractConfigFromFile(fileName) + PageController.goToPage(PageEnum.PageSetupWizardViewConfig) + } + } } } diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index e09882c0..c9f38753 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -81,42 +81,6 @@ Window { function onShowPassphraseRequestDrawer() { privateKeyPassphraseDrawer.open() } - - function onSetupFileDialogForConfig() { - mainFileDialog.acceptLabel = qsTr("Open config file") - mainFileDialog.nameFilters = !ServersModel.getServersCount() ? [ "Config or backup files (*.vpn *.ovpn *.conf *.backup)" ] : - [ "Config files (*.vpn *.ovpn *.conf)" ] - mainFileDialog.acceptFunction = function() { - if (mainFileDialog.selectedFile.toString().indexOf(".backup") !== -1 && !ServersModel.getServersCount()) { - PageController.showBusyIndicator(true) - SettingsController.restoreAppConfig(mainFileDialog.selectedFile.toString()) - PageController.showBusyIndicator(false) - } else { - ImportController.extractConfigFromFile(mainFileDialog.selectedFile) - PageController.goToPage(PageEnum.PageSetupWizardViewConfig) - } - } - } - - function onSetupFileDialogForSites(replaceExistingSites) { - mainFileDialog.acceptLabel = qsTr("Open sites file") - mainFileDialog.nameFilters = [ "Sites files (*.json)" ] - mainFileDialog.acceptFunction = function() { - PageController.showBusyIndicator(true) - SitesController.importSites(mainFileDialog.selectedFile.toString(), replaceExistingSites) - PageController.showBusyIndicator(false) - } - } - - function onSetupFileDialogForBackup() { - mainFileDialog.acceptLabel = qsTr("Open backup file") - mainFileDialog.nameFilters = [ "Backup files (*.backup)" ] - mainFileDialog.acceptFunction = function() { - PageController.showBusyIndicator(true) - SettingsController.restoreAppConfig(mainFileDialog.selectedFile.toString()) - PageController.showBusyIndicator(false) - } - } } Connections { @@ -234,10 +198,12 @@ Window { FileDialog { id: mainFileDialog - property var acceptFunction + property bool isSaveMode: false objectName: "mainFileDialog" + fileMode: isSaveMode ? FileDialog.SaveFile : FileDialog.OpenFile - onAccepted: acceptFunction() + onAccepted: SystemController.fileDialogClosed(true) + onRejected: SystemController.fileDialogClosed(false) } } From fdff57da7c5896fa07bff7b49aeadc45b43da85e Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 8 Sep 2023 18:05:08 +0500 Subject: [PATCH 096/278] fixed navigation during initial installation --- client/platforms/ios/MobileUtils.cpp | 8 +++++-- client/ui/qml/Pages2/PageSetupWizardStart.qml | 22 +++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/client/platforms/ios/MobileUtils.cpp b/client/platforms/ios/MobileUtils.cpp index 3923d291..f94d56fc 100644 --- a/client/platforms/ios/MobileUtils.cpp +++ b/client/platforms/ios/MobileUtils.cpp @@ -1,5 +1,9 @@ #include "MobileUtils.h" -void MobileUtils::shareText(const QStringList&) {} - +void MobileUtils::shareText(const QStringList &) +{ +} +void MobileUtils::openFile() +{ +} diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index 2ce93e53..36d90bd8 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -23,6 +23,28 @@ PageType { function onShowBusyIndicator(visible) { busyIndicator.visible = visible } + + function onClosePage() { + if (stackView.depth <= 1) { + return + } + stackView.pop() + } + + function onGoToPage(page, slide) { + var pagePath = PageController.getPagePath(page) + if (slide) { + stackView.push(pagePath, { "objectName" : pagePath }, StackView.PushTransition) + } else { + stackView.push(pagePath, { "objectName" : pagePath }, StackView.Immediate) + } + } + + function onGoToStartPage() { + while (stackView.depth > 1) { + stackView.pop() + } + } } Connections { From 85414eb65f5eb3fff953f46b9229c613d60d9ff9 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Fri, 8 Sep 2023 21:31:47 +0800 Subject: [PATCH 097/278] fixed protocol reloads old value in settings page --- client/amnezia_application.cpp | 2 +- client/amnezia_application.h | 2 +- client/ui/controllers/installController.cpp | 9 ++++++++- client/ui/controllers/installController.h | 3 +++ client/ui/qml/Pages2/PageProtocolCloakSettings.qml | 10 +++++++++- 5 files changed, 22 insertions(+), 4 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 8db6f8de..1725e1f8 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -330,7 +330,7 @@ void AmneziaApplication::initControllers() m_pageController.reset(new PageController(m_serversModel, m_settings)); m_engine->rootContext()->setContextProperty("PageController", m_pageController.get()); - m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_settings)); + m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_settings)); m_engine->rootContext()->setContextProperty("InstallController", m_installController.get()); connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(), &PageController::showPassphraseRequestDrawer); diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 1ace6d8b..2dd74fcb 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -91,7 +91,7 @@ private: QSharedPointer m_containersModel; QSharedPointer m_serversModel; QSharedPointer m_languageModel; - QScopedPointer m_protocolsModel; + QSharedPointer m_protocolsModel; QSharedPointer m_sitesModel; QScopedPointer m_openVpnConfigModel; diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 1db84b36..c0e9acbb 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -40,8 +40,13 @@ namespace InstallController::InstallController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, + const QSharedPointer &protocolsModel, const std::shared_ptr &settings, QObject *parent) - : QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel), m_settings(settings) + : QObject(parent), + m_serversModel(serversModel), + m_containersModel(containersModel), + m_protocolModel(protocolsModel), + m_settings(settings) { } @@ -252,6 +257,8 @@ void InstallController::updateContainer(QJsonObject config) auto errorCode = serverController.updateContainer(serverCredentials, container, oldContainerConfig, config); if (errorCode == ErrorCode::NoError) { m_containersModel->setData(modelIndex, config, ContainersModel::Roles::ConfigRole); + m_protocolModel->updateModel(config); + emit updateContainerFinished(); return; } diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index 4060c97c..47fc5dab 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -8,6 +8,7 @@ #include "core/defs.h" #include "ui/models/containers_model.h" #include "ui/models/servers_model.h" +#include "ui/models/protocols_model.h" class InstallController : public QObject { @@ -15,6 +16,7 @@ class InstallController : public QObject public: explicit InstallController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, + const QSharedPointer &protocolsModel, const std::shared_ptr &settings, QObject *parent = nullptr); ~InstallController(); @@ -71,6 +73,7 @@ private: QSharedPointer m_serversModel; QSharedPointer m_containersModel; + QSharedPointer m_protocolModel; std::shared_ptr m_settings; ServerCredentials m_currentlyInstalledServerCredentials; diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml index b53fdfdf..e15c5ec7 100644 --- a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -91,7 +91,15 @@ PageType { textField.onEditingFinished: { if (textFieldText !== site) { - site = textFieldText + var tmpText = textFieldText + tmpText = tmpText.toLocaleLowerCase() + + var indexHttps = tmpText.indexOf("https://") + if (indexHttps === 0) { + tmpText = textFieldText.substring(8) + } else { + site = textFieldText + } } } } From 3cfca046ba5ad8ed5d00c18cfd13e0c446697b00 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Fri, 8 Sep 2023 21:40:55 +0800 Subject: [PATCH 098/278] adapted installer wizard to macos style --- deploy/installer/config/macos.xml.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/installer/config/macos.xml.in b/deploy/installer/config/macos.xml.in index 74b682b7..3888d08d 100644 --- a/deploy/installer/config/macos.xml.in +++ b/deploy/installer/config/macos.xml.in @@ -8,7 +8,7 @@ /Applications/AmneziaVPN.app 600 380 - Modern + Mac true true false From 1c7868312dad02d3e1f19248d5160b99974036bf Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sat, 9 Sep 2023 01:29:28 +0500 Subject: [PATCH 099/278] added getting the path to the file for iOS --- client/platforms/ios/MobileUtils.cpp | 3 +- client/platforms/ios/MobileUtils.h | 9 +++-- client/platforms/ios/MobileUtils.mm | 39 ++++++++++++++++++---- client/ui/controllers/systemController.cpp | 5 ++- 4 files changed, 45 insertions(+), 11 deletions(-) diff --git a/client/platforms/ios/MobileUtils.cpp b/client/platforms/ios/MobileUtils.cpp index f94d56fc..fd20fcda 100644 --- a/client/platforms/ios/MobileUtils.cpp +++ b/client/platforms/ios/MobileUtils.cpp @@ -4,6 +4,7 @@ void MobileUtils::shareText(const QStringList &) { } -void MobileUtils::openFile() +QString MobileUtils::openFile() { + return QString(); } diff --git a/client/platforms/ios/MobileUtils.h b/client/platforms/ios/MobileUtils.h index cad9de9e..fe7549c5 100644 --- a/client/platforms/ios/MobileUtils.h +++ b/client/platforms/ios/MobileUtils.h @@ -8,12 +8,15 @@ class MobileUtils : public QObject { Q_OBJECT -public: - MobileUtils() = delete; +//public: +// MobileUtils() = delete; public slots: static void shareText(const QStringList &filesToSend); - static void openFile(); + QString openFile(); + +signals: + void finished(); }; #endif // MOBILEUTILS_H diff --git a/client/platforms/ios/MobileUtils.mm b/client/platforms/ios/MobileUtils.mm index 63ee0364..7f5fc1e6 100644 --- a/client/platforms/ios/MobileUtils.mm +++ b/client/platforms/ios/MobileUtils.mm @@ -4,6 +4,7 @@ #include #include +#include static UIViewController* getViewController() { NSArray *windows = [[UIApplication sharedApplication]windows]; @@ -35,24 +36,33 @@ void MobileUtils::shareText(const QStringList& filesToSend) { } } +typedef void (^FileSelectionCallback)(NSString *fileContent); + @interface MyFilePickerDelegate : NSObject + +@property (nonatomic, copy) FileSelectionCallback fileSelectionCallback; + @end @implementation MyFilePickerDelegate - (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray *)urls { for (NSURL *url in urls) { - NSString *filePath = [url path]; - - NSData *fileData = [NSData dataWithContentsOfFile:filePath]; - NSString *fileContent = [[NSString alloc] initWithData:fileData encoding:NSUTF8StringEncoding]; - NSLog(@"Содержимое файла: %@", fileContent); + if (self.fileSelectionCallback) { + self.fileSelectionCallback([url path]); + } + } +} + +- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller { + if (self.fileSelectionCallback) { + self.fileSelectionCallback(nil); } } @end -void MobileUtils::openFile() { +QString MobileUtils::openFile() { UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.item"] inMode:UIDocumentPickerModeOpen]; MyFilePickerDelegate *filePickerDelegate = [[MyFilePickerDelegate alloc] init]; @@ -62,4 +72,21 @@ void MobileUtils::openFile() { if (!qtController) return; [qtController presentViewController:documentPicker animated:YES completion:nil]; + + __block QString path1; + + filePickerDelegate.fileSelectionCallback = ^(NSString *filePath) { + if (filePath) { + path1 = QString::fromUtf8(filePath.UTF8String); + } else { + path1 = QString(""); + } + emit finished(); + }; + + QEventLoop wait1; + QObject::connect(this, &MobileUtils::finished, &wait1, &QEventLoop::quit); + wait1.exec(); + + return path1; } diff --git a/client/ui/controllers/systemController.cpp b/client/ui/controllers/systemController.cpp index a6210b69..8e1fb6c8 100644 --- a/client/ui/controllers/systemController.cpp +++ b/client/ui/controllers/systemController.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #ifdef Q_OS_ANDROID #include "platforms/android/android_controller.h" @@ -58,8 +59,10 @@ QString SystemController::getFileName(const QString &acceptLabel, const QString { QString fileName; #ifdef Q_OS_IOS - MobileUtils::openFile(); + MobileUtils mobileUtils; + fileName = mobileUtils.openFile(); + CFURLRef url = CFURLCreateWithFileSystemPath( kCFAllocatorDefault, CFStringCreateWithCharacters(0, reinterpret_cast(fileName.unicode()), fileName.length()), From 0a5657738e98544135abebf8d531f5906cecf349 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sat, 9 Sep 2023 15:00:34 +0500 Subject: [PATCH 100/278] added a wait for the file dialog to close when sharing ios files --- client/platforms/ios/MobileUtils.h | 8 +-- client/platforms/ios/MobileUtils.mm | 58 ++++++++++++++-------- client/ui/controllers/systemController.cpp | 7 ++- 3 files changed, 47 insertions(+), 26 deletions(-) diff --git a/client/platforms/ios/MobileUtils.h b/client/platforms/ios/MobileUtils.h index fe7549c5..04a12c51 100644 --- a/client/platforms/ios/MobileUtils.h +++ b/client/platforms/ios/MobileUtils.h @@ -7,12 +7,12 @@ class MobileUtils : public QObject { Q_OBJECT - -//public: -// MobileUtils() = delete; + +public: + explicit MobileUtils(QObject *parent = nullptr); public slots: - static void shareText(const QStringList &filesToSend); + bool shareText(const QStringList &filesToSend); QString openFile(); signals: diff --git a/client/platforms/ios/MobileUtils.mm b/client/platforms/ios/MobileUtils.mm index 7f5fc1e6..fbf26ffd 100644 --- a/client/platforms/ios/MobileUtils.mm +++ b/client/platforms/ios/MobileUtils.mm @@ -3,7 +3,6 @@ #include #include -#include #include static UIViewController* getViewController() { @@ -16,7 +15,11 @@ static UIViewController* getViewController() { return nil; } -void MobileUtils::shareText(const QStringList& filesToSend) { +MobileUtils::MobileUtils(QObject *parent) : QObject(parent) { + +} + +bool MobileUtils::shareText(const QStringList& filesToSend) { NSMutableArray *sharingItems = [NSMutableArray new]; for (int i = 0; i < filesToSend.size(); i++) { @@ -28,35 +31,48 @@ void MobileUtils::shareText(const QStringList& filesToSend) { if (!qtController) return; UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:sharingItems applicationActivities:nil]; + + __block bool isAccepted = false; + + [activityController setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) { + isAccepted = completed; + emit finished(); + }]; [qtController presentViewController:activityController animated:YES completion:nil]; UIPopoverPresentationController *popController = activityController.popoverPresentationController; if (popController) { popController.sourceView = qtController.view; } + + QEventLoop wait; + QObject::connect(this, &MobileUtils::finished, &wait, &QEventLoop::quit); + wait.exec(); + + return isAccepted; } -typedef void (^FileSelectionCallback)(NSString *fileContent); +typedef void (^DocumentPickerClosedCallback)(NSString *path); -@interface MyFilePickerDelegate : NSObject +@interface DocumentPickerDelegate : NSObject -@property (nonatomic, copy) FileSelectionCallback fileSelectionCallback; +@property (nonatomic, copy) DocumentPickerClosedCallback documentPickerClosedCallback; @end -@implementation MyFilePickerDelegate +@implementation DocumentPickerDelegate - (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray *)urls { for (NSURL *url in urls) { - if (self.fileSelectionCallback) { - self.fileSelectionCallback([url path]); + if (self.documentPickerClosedCallback) { + self.documentPickerClosedCallback([url path]); } } } - (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller { - if (self.fileSelectionCallback) { - self.fileSelectionCallback(nil); + if (self.documentPickerClosedCallback) { + self.documentPickerClosedCallback(nil); } } @@ -65,28 +81,28 @@ typedef void (^FileSelectionCallback)(NSString *fileContent); QString MobileUtils::openFile() { UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.item"] inMode:UIDocumentPickerModeOpen]; - MyFilePickerDelegate *filePickerDelegate = [[MyFilePickerDelegate alloc] init]; - documentPicker.delegate = filePickerDelegate; + DocumentPickerDelegate *documentPickerDelegate = [[DocumentPickerDelegate alloc] init]; + documentPicker.delegate = documentPickerDelegate; UIViewController *qtController = getViewController(); if (!qtController) return; [qtController presentViewController:documentPicker animated:YES completion:nil]; - __block QString path1; + __block QString filePath; - filePickerDelegate.fileSelectionCallback = ^(NSString *filePath) { - if (filePath) { - path1 = QString::fromUtf8(filePath.UTF8String); + documentPickerDelegate.documentPickerClosedCallback = ^(NSString *path) { + if (path) { + filePath = QString::fromUtf8(path.UTF8String); } else { - path1 = QString(""); + filePath = QString(); } emit finished(); }; - QEventLoop wait1; - QObject::connect(this, &MobileUtils::finished, &wait1, &QEventLoop::quit); - wait1.exec(); + QEventLoop wait; + QObject::connect(this, &MobileUtils::finished, &wait, &QEventLoop::quit); + wait.exec(); - return path1; + return filePath; } diff --git a/client/ui/controllers/systemController.cpp b/client/ui/controllers/systemController.cpp index 8e1fb6c8..7de071cc 100644 --- a/client/ui/controllers/systemController.cpp +++ b/client/ui/controllers/systemController.cpp @@ -46,7 +46,9 @@ void SystemController::saveFile(QString fileName, const QString &data) #ifdef Q_OS_IOS QStringList filesToSend; filesToSend.append(fileUrl.toString()); - MobileUtils::shareText(filesToSend); + MobileUtils mobileUtils; + // todo check if save successful + mobileUtils.shareText(filesToSend); return; #else QFileInfo fi(fileName); @@ -62,6 +64,9 @@ QString SystemController::getFileName(const QString &acceptLabel, const QString MobileUtils mobileUtils; fileName = mobileUtils.openFile(); + if (fileName.isEmpty()) { + return fileName; + } CFURLRef url = CFURLCreateWithFileSystemPath( kCFAllocatorDefault, From 4bf6cce4ba3792fb9d5a16310207467d197c0ba9 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sat, 9 Sep 2023 18:25:44 +0500 Subject: [PATCH 101/278] fixed return type of sharedText function --- client/platforms/ios/MobileUtils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/platforms/ios/MobileUtils.cpp b/client/platforms/ios/MobileUtils.cpp index fd20fcda..18378113 100644 --- a/client/platforms/ios/MobileUtils.cpp +++ b/client/platforms/ios/MobileUtils.cpp @@ -1,6 +1,6 @@ #include "MobileUtils.h" -void MobileUtils::shareText(const QStringList &) +bool MobileUtils::shareText(const QStringList &) { } From 89096554e83471caabd9da8dcfcd0878e616c3a4 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sat, 9 Sep 2023 18:31:04 +0500 Subject: [PATCH 102/278] added constructor for MobileUtils.cpp --- client/platforms/ios/MobileUtils.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/platforms/ios/MobileUtils.cpp b/client/platforms/ios/MobileUtils.cpp index 18378113..14c0854a 100644 --- a/client/platforms/ios/MobileUtils.cpp +++ b/client/platforms/ios/MobileUtils.cpp @@ -1,7 +1,12 @@ #include "MobileUtils.h" +MobileUtils::MobileUtils(QObject *parent) : QObject(parent) +{ +} + bool MobileUtils::shareText(const QStringList &) { + return false; } QString MobileUtils::openFile() From f751657903f13fda0dd59c45c1ab3a094d28b13d Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sat, 9 Sep 2023 22:41:36 +0500 Subject: [PATCH 103/278] fixed false triggering of swipes for the main menu drawer of PageHome --- client/ui/qml/Pages2/PageHome.qml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 0c778215..01ba3032 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -129,7 +129,13 @@ PageType { DrawerType { id: menu - interactive: true + interactive: { + if (stackView && stackView.currentItem) { + return (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) ? true : false + } else { + return false + } + } dragMargin: buttonBackground.height + 56 // page start tabBar height width: parent.width From 551f7616f00aac45b86e1c82c0efe5aac583aad4 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 11 Sep 2023 13:56:49 +0500 Subject: [PATCH 104/278] fixed the display of the protocol list in the settings when attempting to install a container that is already installed on the server --- client/core/servercontroller.cpp | 25 ++++++++++++++++------ client/ui/qml/Pages2/PageSettingsAbout.qml | 2 +- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/client/core/servercontroller.cpp b/client/core/servercontroller.cpp index ef7a8024..b0f8146f 100644 --- a/client/core/servercontroller.cpp +++ b/client/core/servercontroller.cpp @@ -665,7 +665,8 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential QString transportProto = containerConfig.value(config_key::transport_proto).toString(defaultTransportProto); // TODO reimplement with netstat - QString script = QString("which lsof &>/dev/null || true && sudo lsof -i -P -n 2>/dev/null | grep -E ':%1 ").arg(port); + QString script = + QString("which lsof &>/dev/null || true && sudo lsof -i -P -n 2>/dev/null | grep -E ':%1 ").arg(port); for (auto &port : fixedPorts) { script = script.append("|:%1").arg(port); } @@ -739,8 +740,10 @@ ErrorCode ServerController::isServerDpkgBusy(const ServerCredentials &credential genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr); - if (stdOut.contains("Packet manager not found")) return ErrorCode::ServerPacketManagerError; - if (stdOut.contains("fuser not installed")) return ErrorCode::NoError; + if (stdOut.contains("Packet manager not found")) + return ErrorCode::ServerPacketManagerError; + if (stdOut.contains("fuser not installed")) + return ErrorCode::NoError; if (stdOut.isEmpty()) { return ErrorCode::NoError; @@ -798,11 +801,19 @@ ErrorCode ServerController::getAlreadyInstalledContainers(const ServerCredential QString port = containerAndPortMatch.captured(2); QString transportProto = containerAndPortMatch.captured(3); DockerContainer container = ContainerProps::containerFromString(name); + + QJsonObject config; Proto mainProto = ContainerProps::defaultProtocol(container); - QJsonObject config { { config_key::container, name }, - { ProtocolProps::protoToString(mainProto), - QJsonObject { { config_key::port, port }, - { config_key::transport_proto, transportProto } } } }; + for (auto protocol : ContainerProps::protocolsForContainer(container)) { + QJsonObject containerConfig; + if (protocol == mainProto) { + containerConfig.insert(config_key::port, port); + containerConfig.insert(config_key::transport_proto, transportProto); + + config.insert(config_key::container, ContainerProps::containerToString(container)); + } + config.insert(ProtocolProps::protoToString(protocol), containerConfig); + } installedContainers.insert(container, config); } } diff --git a/client/ui/qml/Pages2/PageSettingsAbout.qml b/client/ui/qml/Pages2/PageSettingsAbout.qml index 1a6f7e80..e73ef88f 100644 --- a/client/ui/qml/Pages2/PageSettingsAbout.qml +++ b/client/ui/qml/Pages2/PageSettingsAbout.qml @@ -122,7 +122,7 @@ And if you don't like the app, all the more support it - the donation will be us leftImageSource: "qrc:/images/controls/telegram.svg" clickedFunction: function() { - Qt.openUrlExternally(qsTr("https://t.me/amnezia_vpn_dev")) + Qt.openUrlExternally(qsTr("https://t.me/amnezia_vpn_en")) } } From 844b552bf3ebfd86662fd83f29f9e420b28bb69d Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 11 Sep 2023 14:45:10 +0500 Subject: [PATCH 105/278] removed duplicate function routeMode --- client/settings.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/client/settings.cpp b/client/settings.cpp index 80a6be26..93871834 100644 --- a/client/settings.cpp +++ b/client/settings.cpp @@ -240,15 +240,6 @@ Settings::RouteMode Settings::routeMode() const return static_cast(m_settings.value("Conf/routeMode", 0).toInt()); } -Settings::RouteMode Settings::routeMode() const -{ -// TODO implement for mobiles -#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) - return RouteMode::VpnAllSites; -#endif - return static_cast(m_settings.value("Conf/routeMode", 0).toInt()); -} - bool Settings::addVpnSite(RouteMode mode, const QString &site, const QString &ip) { QVariantMap sites = vpnSites(mode); From 97c0fe1ece0805784e860dee618aa879e53956aa Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 11 Sep 2023 14:55:52 +0500 Subject: [PATCH 106/278] increased the application version to 4.0.5.1 --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 875d664d..49b8fa16 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,11 +2,11 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.0.4.1 +project(${PROJECT} VERSION 4.0.5.1 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) -set(RELEASE_DATE "2023-08-26") +set(RELEASE_DATE "2023-09-11") set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") From 72eb36f5b3f38daf8836fd6eecade21103d58e63 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Mon, 11 Sep 2023 18:55:50 +0800 Subject: [PATCH 107/278] fixed the implicitWidth error in protocol installation page --- client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 6552d873..07eef177 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -145,6 +145,7 @@ PageType { } TextField { + implicitWidth: parent.width Layout.fillWidth: true Layout.topMargin: 16 Layout.bottomMargin: 16 From f8e5e9f675e01373bd82115f6a50777e7e2ed6fa Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 11 Sep 2023 16:48:56 +0500 Subject: [PATCH 108/278] fixed display of cursorShape for all mouseArea --- client/ui/qml/Controls2/PageType.qml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/client/ui/qml/Controls2/PageType.qml b/client/ui/qml/Controls2/PageType.qml index 193fbcf2..2c176b40 100644 --- a/client/ui/qml/Controls2/PageType.qml +++ b/client/ui/qml/Controls2/PageType.qml @@ -7,15 +7,16 @@ Item { property StackView stackView: StackView.view - MouseArea { - z: 99 - anchors.fill: parent +// MouseArea { +// id: globalMouseArea +// z: 99 +// anchors.fill: parent - enabled: true +// enabled: true - onPressed: function(mouse) { - forceActiveFocus() - mouse.accepted = false - } - } +// onPressed: function(mouse) { +// forceActiveFocus() +// mouse.accepted = false +// } +// } } From a964d955f41ee36ba12a1a5db615f71ced7f5c83 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Mon, 11 Sep 2023 23:49:50 +0800 Subject: [PATCH 109/278] fixed scroll stuck on textarea in page OpenVpnSettings --- client/ui/qml/Controls2/TextAreaType.qml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/client/ui/qml/Controls2/TextAreaType.qml b/client/ui/qml/Controls2/TextAreaType.qml index a75ea55d..9b9921f8 100644 --- a/client/ui/qml/Controls2/TextAreaType.qml +++ b/client/ui/qml/Controls2/TextAreaType.qml @@ -15,6 +15,9 @@ Rectangle { radius: 16 FlickableType { + id: fl + interactive: false + anchors.top: parent.top anchors.bottom: parent.bottom contentHeight: textArea.implicitHeight @@ -46,12 +49,23 @@ Rectangle { } } + onCursorVisibleChanged: { + if (textArea.cursorVisible) { + fl.interactive = true + } else { + fl.interactive = false + } + } + wrapMode: Text.Wrap MouseArea { anchors.fill: parent acceptedButtons: Qt.RightButton - onClicked: contextMenu.open() + onClicked: { + fl.interactive = true + contextMenu.open() + } } ContextMenuType { From f81ee1b267148295e40d7fc3453e1bd74f8938c0 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Tue, 12 Sep 2023 21:38:36 +0800 Subject: [PATCH 110/278] reconnect to server when changed the protocol and status is connected or connnecting --- .../ui/qml/Components/HomeContainersListView.qml | 14 ++++++++++++++ client/ui/qml/Pages2/PageHome.qml | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index f5d27c00..e265574b 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -62,8 +62,22 @@ ListView { onClicked: { if (checked) { isDefault = true + var needReconnected = false + if (menuContent.currentIndex !== index) { + needReconnected = true + } + menuContent.currentIndex = index containersDropDown.menuVisible = false + + + if (needReconnected && + (ConnectionController.isConnected || ConnectionController.isConnectionInProgress)) { + PageController.showNotificationMessage(qsTr("Reconnect via VPN Procotol: ") + name) + PageController.goToPageHome() + menu.visible = false + ConnectionController.openConnection() + } } else { ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(index)) InstallController.setShouldCreateServer(false) diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 01ba3032..b9bc2366 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -210,7 +210,7 @@ PageType { } Component.onCompleted: updateContainersModelFilters() - currentIndex: ContainersModel.getDefaultContainer() + currentIndex: ContainersModel.getDefaultContainer() - 1 } } } From 9c0f27edb49bbaa5605ca47fc83a54173b8a44a6 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Wed, 13 Sep 2023 08:01:14 +0800 Subject: [PATCH 111/278] removed invalid codes --- client/ui/qml/Components/HomeContainersListView.qml | 5 +++-- client/ui/qml/Pages2/PageHome.qml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index e265574b..4708128f 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -61,12 +61,13 @@ ListView { onClicked: { if (checked) { - isDefault = true var needReconnected = false - if (menuContent.currentIndex !== index) { + if (!isDefault) { needReconnected = true } + isDefault = true + menuContent.currentIndex = index containersDropDown.menuVisible = false diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index b9bc2366..cedff2cb 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -210,7 +210,7 @@ PageType { } Component.onCompleted: updateContainersModelFilters() - currentIndex: ContainersModel.getDefaultContainer() - 1 + // currentIndex: ContainersModel.getDefaultContainer() - 1 } } } From 3c9b42b9f73db40a652b3fac3e2b84706c5777f5 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Wed, 13 Sep 2023 08:44:50 +0800 Subject: [PATCH 112/278] deleted unused code --- client/ui/qml/Pages2/PageHome.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index cedff2cb..c5ba89d0 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -210,7 +210,6 @@ PageType { } Component.onCompleted: updateContainersModelFilters() - // currentIndex: ContainersModel.getDefaultContainer() - 1 } } } From 16cadfeae804885fdd5ce9561883410063d079d3 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Wed, 13 Sep 2023 09:39:17 +0800 Subject: [PATCH 113/278] the cursor changes to Qt.PointingHandCursor when hovering over links --- client/ui/qml/Pages2/PageServiceSftpSettings.qml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/ui/qml/Pages2/PageServiceSftpSettings.qml b/client/ui/qml/Pages2/PageServiceSftpSettings.qml index fead034b..9287bd20 100644 --- a/client/ui/qml/Pages2/PageServiceSftpSettings.qml +++ b/client/ui/qml/Pages2/PageServiceSftpSettings.qml @@ -218,6 +218,11 @@ PageType { } + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.NoButton + cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor + } } BasicButtonType { From e2aef1fc1d08c7a145de233e82791d6526d36ef5 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 13 Sep 2023 11:09:29 +0500 Subject: [PATCH 114/278] fixed display of installation errors on the initial installation screen --- client/ui/qml/Pages2/PageSetupWizardStart.qml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index 36d90bd8..9f5e57a5 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -56,6 +56,20 @@ PageType { } } + Connections { + target: InstallController + + function onInstallationErrorOccurred(errorMessage) { + PageController.showErrorMessage(errorMessage) + + var currentPageName = tabBarStackView.currentItem.objectName + + if (currentPageName === PageController.getPagePath(PageEnum.PageSetupWizardInstalling)) { + PageController.closePage() + } + } + } + FlickableType { id: fl anchors.top: parent.top From 4ae608ed936100fb7d7f8369cc5ecf11fcc45c37 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 13 Sep 2023 16:11:08 +0500 Subject: [PATCH 115/278] added import backup file from outside for ios --- client/amnezia_application.cpp | 5 + client/containers/containers_defs.cpp | 4 +- client/platforms/ios/QtAppDelegate.mm | 14 +- client/platforms/ios/ios_controller.h | 50 +++--- client/protocols/protocols_defs.cpp | 149 +++++++++--------- client/ui/controllers/pageController.h | 1 + client/ui/controllers/settingsController.h | 2 + .../qml/Components/ShareConnectionDrawer.qml | 4 +- client/ui/qml/Pages2/PageSettingsBackup.qml | 35 +++- client/ui/qml/main2.qml | 4 + 10 files changed, 157 insertions(+), 111 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 1725e1f8..de75db85 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -117,6 +117,11 @@ void AmneziaApplication::init() &ImportController::extractConfigFromData); connect(IosController::Instance(), &IosController::importConfigFromOutside, m_pageController.get(), &PageController::goToPageViewConfig); + + connect(IosController::Instance(), &IosController::importBackupFromOutside, m_pageController.get(), + &PageController::goToPageSettingsBackup); + connect(IosController::Instance(), &IosController::importBackupFromOutside, m_settingsController.get(), + &SettingsController::importBackupFromOutside); #endif m_notificationHandler.reset(NotificationHandler::create(nullptr)); diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index 98b2d0da..766d89da 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -86,7 +86,7 @@ QMap ContainerProps::containerHumanNames() { DockerContainer::WireGuard, "WireGuard" }, { DockerContainer::Ipsec, QObject::tr("IPsec") }, - { DockerContainer::TorWebSite, QObject::tr("Web site in Tor network") }, + { DockerContainer::TorWebSite, QObject::tr("Website in Tor network") }, { DockerContainer::Dns, QObject::tr("Amnezia DNS") }, //{DockerContainer::FileShare, QObject::tr("SMB file sharing service")}, { DockerContainer::Sftp, QObject::tr("Sftp file sharing service") } }; @@ -129,7 +129,7 @@ QMap ContainerProps::containerDetailedDescriptions() { DockerContainer::WireGuard, QObject::tr("WireGuard container") }, { DockerContainer::Ipsec, QObject::tr("IPsec container") }, - { DockerContainer::TorWebSite, QObject::tr("Web site in Tor network") }, + { DockerContainer::TorWebSite, QObject::tr("Website in Tor network") }, { DockerContainer::Dns, QObject::tr("DNS Service") }, //{DockerContainer::FileShare, QObject::tr("SMB file sharing service - is Window file sharing protocol")}, { DockerContainer::Sftp, QObject::tr("Sftp file sharing service - is secure FTP service") } }; diff --git a/client/platforms/ios/QtAppDelegate.mm b/client/platforms/ios/QtAppDelegate.mm index 7528f2ff..48cef914 100644 --- a/client/platforms/ios/QtAppDelegate.mm +++ b/client/platforms/ios/QtAppDelegate.mm @@ -76,11 +76,15 @@ QString filePath(url.path.UTF8String); if (filePath.isEmpty()) return NO; - QFile file(filePath); - bool isOpenFile = file.open(QIODevice::ReadOnly); - QByteArray data = file.readAll(); - - IosController::Instance()->importConfigFromOutside(QString(data)); + if (filePath.contains("backup")) { + IosController::Instance()->importBackupFromOutside(filePath); + } else { + QFile file(filePath); + bool isOpenFile = file.open(QIODevice::ReadOnly); + QByteArray data = file.readAll(); + + IosController::Instance()->importConfigFromOutside(QString(data)); + } return YES; } return NO; diff --git a/client/platforms/ios/ios_controller.h b/client/platforms/ios/ios_controller.h index a81498af..ea8adbc0 100644 --- a/client/platforms/ios/ios_controller.h +++ b/client/platforms/ios/ios_controller.h @@ -4,28 +4,30 @@ #include "protocols/vpnprotocol.h" #ifdef __OBJC__ -#import + #import @class NETunnelProviderManager; #endif using namespace amnezia; -struct Action { - static const char* start; - static const char* restart; - static const char* stop; - static const char* getTunnelId; - static const char* getStatus; +struct Action +{ + static const char *start; + static const char *restart; + static const char *stop; + static const char *getTunnelId; + static const char *getStatus; }; -struct MessageKey { - static const char* action; - static const char* tunnelId; - static const char* config; - static const char* errorCode; - static const char* host; - static const char* port; - static const char* isOnDemand; +struct MessageKey +{ + static const char *action; + static const char *tunnelId; + static const char *config; + static const char *errorCode; + static const char *host; + static const char *port; + static const char *isOnDemand; }; class IosController : public QObject @@ -33,27 +35,27 @@ class IosController : public QObject Q_OBJECT public: - static IosController* Instance(); + static IosController *Instance(); virtual ~IosController() override = default; bool initialize(); - bool connectVpn(amnezia::Proto proto, const QJsonObject& configuration); + bool connectVpn(amnezia::Proto proto, const QJsonObject &configuration); void disconnectVpn(); void vpnStatusDidChange(void *pNotification); void vpnConfigurationDidChange(void *pNotification); - void getBackendLogs(std::function &&callback); + void getBackendLogs(std::function &&callback); void checkStatus(); signals: void connectionStateChanged(Vpn::ConnectionState state); void bytesChanged(quint64 receivedBytes, quint64 sentBytes); void importConfigFromOutside(const QString); + void importBackupFromOutside(const QString); protected slots: - private: explicit IosController(); @@ -67,12 +69,12 @@ private: void startTunnel(); private: - void *m_iosControllerWrapper{}; + void *m_iosControllerWrapper {}; #ifdef __OBJC__ - NETunnelProviderManager *m_currentTunnel{}; - NSString *m_serverAddress{}; - bool isOurManager(NETunnelProviderManager* manager); - void sendVpnExtensionMessage(NSDictionary* message, std::function callback = nullptr); + NETunnelProviderManager *m_currentTunnel {}; + NSString *m_serverAddress {}; + bool isOurManager(NETunnelProviderManager *manager); + void sendVpnExtensionMessage(NSDictionary *message, std::function callback = nullptr); #endif amnezia::Proto m_proto; diff --git a/client/protocols/protocols_defs.cpp b/client/protocols/protocols_defs.cpp index 63351311..5f8600db 100644 --- a/client/protocols/protocols_defs.cpp +++ b/client/protocols/protocols_defs.cpp @@ -10,17 +10,21 @@ QDebug operator<<(QDebug debug, const amnezia::ProtocolEnumNS::Proto &p) return debug; } -amnezia::Proto ProtocolProps::protoFromString(QString proto){ +amnezia::Proto ProtocolProps::protoFromString(QString proto) +{ QMetaEnum metaEnum = QMetaEnum::fromType(); for (int i = 0; i < metaEnum.keyCount(); ++i) { Proto p = static_cast(i); - if (proto == protoToString(p)) return p; + if (proto == protoToString(p)) + return p; } return Proto::Any; } -QString ProtocolProps::protoToString(amnezia::Proto p){ - if (p == Proto::Any) return ""; +QString ProtocolProps::protoToString(amnezia::Proto p) +{ + if (p == Proto::Any) + return ""; QMetaEnum metaEnum = QMetaEnum::fromType(); QString protoKey = metaEnum.valueToKey(static_cast(p)); @@ -43,7 +47,8 @@ TransportProto ProtocolProps::transportProtoFromString(QString p) QMetaEnum metaEnum = QMetaEnum::fromType(); for (int i = 0; i < metaEnum.keyCount(); ++i) { TransportProto tp = static_cast(i); - if (p.toLower() == transportProtoToString(tp).toLower()) return tp; + if (p.toLower() == transportProtoToString(tp).toLower()) + return tp; } return TransportProto::Udp; } @@ -55,22 +60,19 @@ QString ProtocolProps::transportProtoToString(TransportProto proto, Proto p) return protoKey.toLower(); } - QMap ProtocolProps::protocolHumanNames() { - return { - {Proto::OpenVpn, "OpenVPN"}, - {Proto::ShadowSocks, "ShadowSocks"}, - {Proto::Cloak, "Cloak"}, - {Proto::WireGuard, "WireGuard"}, - {Proto::Ikev2, "IKEv2"}, - {Proto::L2tp, "L2TP"}, + return { { Proto::OpenVpn, "OpenVPN" }, + { Proto::ShadowSocks, "ShadowSocks" }, + { Proto::Cloak, "Cloak" }, + { Proto::WireGuard, "WireGuard" }, + { Proto::Ikev2, "IKEv2" }, + { Proto::L2tp, "L2TP" }, - {Proto::TorWebSite, "Web site in Tor network"}, - {Proto::Dns, "DNS Service"}, - {Proto::FileShare, "File Sharing Service"}, - {Proto::Sftp, QObject::tr("Sftp service")} - }; + { Proto::TorWebSite, "Website in Tor network" }, + { Proto::Dns, "DNS Service" }, + { Proto::FileShare, "File Sharing Service" }, + { Proto::Sftp, QObject::tr("Sftp service") } }; } QMap ProtocolProps::protocolDescriptions() @@ -81,90 +83,89 @@ QMap ProtocolProps::protocolDescriptions() amnezia::ServiceType ProtocolProps::protocolService(Proto p) { switch (p) { - case Proto::Any : return ServiceType::None; - case Proto::OpenVpn : return ServiceType::Vpn; - case Proto::Cloak : return ServiceType::Vpn; - case Proto::ShadowSocks : return ServiceType::Vpn; - case Proto::WireGuard : return ServiceType::Vpn; - case Proto::TorWebSite : return ServiceType::Other; - case Proto::Dns : return ServiceType::Other; - case Proto::FileShare : return ServiceType::Other; - default: return ServiceType::Other; + case Proto::Any: return ServiceType::None; + case Proto::OpenVpn: return ServiceType::Vpn; + case Proto::Cloak: return ServiceType::Vpn; + case Proto::ShadowSocks: return ServiceType::Vpn; + case Proto::WireGuard: return ServiceType::Vpn; + case Proto::TorWebSite: return ServiceType::Other; + case Proto::Dns: return ServiceType::Other; + case Proto::FileShare: return ServiceType::Other; + default: return ServiceType::Other; } } int ProtocolProps::defaultPort(Proto p) { switch (p) { - case Proto::Any : return -1; - case Proto::OpenVpn : return 1194; - case Proto::Cloak : return 443; - case Proto::ShadowSocks : return 6789; - case Proto::WireGuard : return 51820; - case Proto::Ikev2 : return -1; - case Proto::L2tp : return -1; + case Proto::Any: return -1; + case Proto::OpenVpn: return 1194; + case Proto::Cloak: return 443; + case Proto::ShadowSocks: return 6789; + case Proto::WireGuard: return 51820; + case Proto::Ikev2: return -1; + case Proto::L2tp: return -1; - case Proto::TorWebSite : return -1; - case Proto::Dns : return 53; - case Proto::FileShare : return 139; - case Proto::Sftp : return 222; - default: return -1; + case Proto::TorWebSite: return -1; + case Proto::Dns: return 53; + case Proto::FileShare: return 139; + case Proto::Sftp: return 222; + default: return -1; } } bool ProtocolProps::defaultPortChangeable(Proto p) { switch (p) { - case Proto::Any : return false; - case Proto::OpenVpn : return true; - case Proto::Cloak : return true; - case Proto::ShadowSocks : return true; - case Proto::WireGuard : return true; - case Proto::Ikev2 : return false; - case Proto::L2tp : return false; + case Proto::Any: return false; + case Proto::OpenVpn: return true; + case Proto::Cloak: return true; + case Proto::ShadowSocks: return true; + case Proto::WireGuard: return true; + case Proto::Ikev2: return false; + case Proto::L2tp: return false; - - case Proto::TorWebSite : return true; - case Proto::Dns : return false; - case Proto::FileShare : return false; - default: return -1; + case Proto::TorWebSite: return true; + case Proto::Dns: return false; + case Proto::FileShare: return false; + default: return -1; } } TransportProto ProtocolProps::defaultTransportProto(Proto p) { switch (p) { - case Proto::Any : return TransportProto::Udp; - case Proto::OpenVpn : return TransportProto::Udp; - case Proto::Cloak : return TransportProto::Tcp; - case Proto::ShadowSocks : return TransportProto::Tcp; - case Proto::WireGuard : return TransportProto::Udp; - case Proto::Ikev2 : return TransportProto::Udp; - case Proto::L2tp : return TransportProto::Udp; + case Proto::Any: return TransportProto::Udp; + case Proto::OpenVpn: return TransportProto::Udp; + case Proto::Cloak: return TransportProto::Tcp; + case Proto::ShadowSocks: return TransportProto::Tcp; + case Proto::WireGuard: return TransportProto::Udp; + case Proto::Ikev2: return TransportProto::Udp; + case Proto::L2tp: return TransportProto::Udp; // non-vpn - case Proto::TorWebSite : return TransportProto::Tcp; - case Proto::Dns : return TransportProto::Udp; - case Proto::FileShare : return TransportProto::Udp; - case Proto::Sftp : return TransportProto::Tcp; + case Proto::TorWebSite: return TransportProto::Tcp; + case Proto::Dns: return TransportProto::Udp; + case Proto::FileShare: return TransportProto::Udp; + case Proto::Sftp: return TransportProto::Tcp; } } bool ProtocolProps::defaultTransportProtoChangeable(Proto p) { switch (p) { - case Proto::Any : return false; - case Proto::OpenVpn : return true; - case Proto::Cloak : return false; - case Proto::ShadowSocks : return false; - case Proto::WireGuard : return false; - case Proto::Ikev2 : return false; - case Proto::L2tp : return false; + case Proto::Any: return false; + case Proto::OpenVpn: return true; + case Proto::Cloak: return false; + case Proto::ShadowSocks: return false; + case Proto::WireGuard: return false; + case Proto::Ikev2: return false; + case Proto::L2tp: return false; // non-vpn - case Proto::TorWebSite : return false; - case Proto::Dns : return false; - case Proto::FileShare : return false; - case Proto::Sftp : return false; - default: return false; + case Proto::TorWebSite: return false; + case Proto::Dns: return false; + case Proto::FileShare: return false; + case Proto::Sftp: return false; + default: return false; } } diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 8d3da507..07e77283 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -85,6 +85,7 @@ signals: void goToPageSettings(); void goToPageViewConfig(); void goToPageSettingsServerServices(); + void goToPageSettingsBackup(); void closePage(); diff --git a/client/ui/controllers/settingsController.h b/client/ui/controllers/settingsController.h index 3d96dc03..5a51a3b4 100644 --- a/client/ui/controllers/settingsController.h +++ b/client/ui/controllers/settingsController.h @@ -65,6 +65,8 @@ signals: void saveFile(const QString &fileName, const QString &data); + void importBackupFromOutside(QString filePath); + private: QSharedPointer m_serversModel; QSharedPointer m_containersModel; diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 4d719d1a..6513ea7f 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -169,7 +169,7 @@ DrawerType { Layout.topMargin: 16 } - TextArea { + TextField { id: configText Layout.fillWidth: true @@ -180,6 +180,8 @@ DrawerType { leftPadding: 0 height: 24 + readOnly: true + color: "#D7D8DB" selectionColor: "#633303" selectedTextColor: "#D7D8DB" diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml index 6a9ea58f..7a556dfb 100644 --- a/client/ui/qml/Pages2/PageSettingsBackup.qml +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -10,6 +10,7 @@ import PageEnum 1.0 import "./" import "../Controls2" import "../Config" +import "../Components" import "../Controls2/TextTypes" PageType { @@ -27,6 +28,10 @@ PageType { //goToStartPage() PageController.goToPageHome() } + + function onImportBackupFromOutside(filePath) { + restoreBackup(filePath) + } } BackButtonType { @@ -116,15 +121,35 @@ PageType { text: qsTr("Restore from backup") onClicked: { - var fileName = SystemController.getFileName(qsTr("Open backup file"), + var filePath = SystemController.getFileName(qsTr("Open backup file"), qsTr("Backup files (*.backup)")) - if (fileName !== "") { - PageController.showBusyIndicator(true) - SettingsController.restoreAppConfig(fileName) - PageController.showBusyIndicator(false) + if (filePath !== "") { + restoreBackup(filePath) } } } } } + + function restoreBackup(filePath) { + questionDrawer.headerText = qsTr("Import settings from a backup file?") + questionDrawer.descriptionText = qsTr("All current settings will be reset"); + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + PageController.showBusyIndicator(true) + SettingsController.restoreAppConfig(filePath) + PageController.showBusyIndicator(false) + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true + } + + QuestionDrawer { + id: questionDrawer + } } diff --git a/client/ui/qml/main2.qml b/client/ui/qml/main2.qml index c9f38753..073fb4ad 100644 --- a/client/ui/qml/main2.qml +++ b/client/ui/qml/main2.qml @@ -81,6 +81,10 @@ Window { function onShowPassphraseRequestDrawer() { privateKeyPassphraseDrawer.open() } + + function onGoToPageSettingsBackup() { + PageController.goToPage(PageEnum.PageSettingsBackup) + } } Connections { From d93b5a7b5c0e4657f6ae34a94604cee055676cf1 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 13 Sep 2023 16:34:03 +0500 Subject: [PATCH 116/278] fixed saving of configs for mobile platforms --- client/ui/qml/Components/ShareConnectionDrawer.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 6513ea7f..3a8a4175 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -33,7 +33,7 @@ DrawerType { onClosed: { configExtension = ".vpn" configCaption = qsTr("Save AmneziaVPN config") - configFileName = "amnezia_config.vpn" + configFileName = "amnezia_config" } Item { @@ -74,7 +74,7 @@ DrawerType { onClicked: { var fileName = "" if (GC.isMobile()) { - fileName = configFileName + fileName = configFileName + configExtension } else { fileName = SystemController.getFileName(configCaption, qsTr("Config files (*" + configExtension + ")"), From bfc8c10f3de9cb46170fbe059e378d655193999d Mon Sep 17 00:00:00 2001 From: ronoaer Date: Wed, 13 Sep 2023 20:49:44 +0800 Subject: [PATCH 117/278] auto reconection when current-server's defaul container hase been changed --- client/amnezia_application.cpp | 2 ++ client/core/servercontroller.cpp | 8 +++----- client/core/servercontroller.h | 12 ++++++++---- client/ui/controllers/connectionController.cpp | 8 ++++++++ client/ui/controllers/connectionController.h | 3 +++ client/ui/controllers/installController.cpp | 18 ++++++++++++++++-- client/ui/controllers/installController.h | 4 +++- .../qml/Pages2/PageProtocolCloakSettings.qml | 12 ++++++++++-- .../qml/Pages2/PageProtocolOpenVpnSettings.qml | 12 ++++++++++-- .../Pages2/PageProtocolShadowSocksSettings.qml | 13 +++++++++++-- 10 files changed, 74 insertions(+), 18 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 1725e1f8..cdcee5f4 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -336,6 +336,8 @@ void AmneziaApplication::initControllers() &PageController::showPassphraseRequestDrawer); connect(m_pageController.get(), &PageController::passphraseRequestDrawerClosed, m_installController.get(), &InstallController::setEncryptedPassphrase); + connect(m_installController.get(), &InstallController::currentContainerChanged, m_connectionController.get(), + &ConnectionController::onCurrentContainerChanged); m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings)); m_engine->rootContext()->setContextProperty("ImportController", m_importController.get()); diff --git a/client/core/servercontroller.cpp b/client/core/servercontroller.cpp index b0f8146f..a82785eb 100644 --- a/client/core/servercontroller.cpp +++ b/client/core/servercontroller.cpp @@ -290,13 +290,11 @@ ErrorCode ServerController::setupContainer(const ServerCredentials &credentials, return startupContainerWorker(credentials, container, config); } -ErrorCode ServerController::updateContainer(const ServerCredentials &credentials, DockerContainer container, +ErrorCode ServerController::updateContainer(const bool reinstallRequired, + const ServerCredentials &credentials, + DockerContainer container, const QJsonObject &oldConfig, QJsonObject &newConfig) { - bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig); - qDebug() << "ServerController::updateContainer for container" << container << "reinstall required is" - << reinstallRequired; - if (reinstallRequired) { return setupContainer(credentials, container, newConfig, true); } else { diff --git a/client/core/servercontroller.h b/client/core/servercontroller.h index cb74d571..d3f242a3 100644 --- a/client/core/servercontroller.h +++ b/client/core/servercontroller.h @@ -26,8 +26,11 @@ public: ErrorCode removeContainer(const ServerCredentials &credentials, DockerContainer container); ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config, bool isUpdate = false); - ErrorCode updateContainer(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &oldConfig, QJsonObject &newConfig); + ErrorCode updateContainer(const bool reinstallRequired, const ServerCredentials &credentials, + DockerContainer container, + const QJsonObject &oldConfig, + QJsonObject &newConfig); + ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials, QMap &installedContainers); @@ -60,6 +63,8 @@ public: ErrorCode getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, const std::function &callback); + bool isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, + const QJsonObject &newConfig); private: ErrorCode installDockerWorker(const ServerCredentials &credentials, DockerContainer container); ErrorCode prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, @@ -72,8 +77,7 @@ private: ErrorCode isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config); - bool isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, - const QJsonObject &newConfig); + ErrorCode isUserInSudo(const ServerCredentials &credentials, DockerContainer container); ErrorCode isServerDpkgBusy(const ServerCredentials &credentials, DockerContainer container); diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index 0754b024..180d96e7 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -118,6 +118,14 @@ void ConnectionController::onConnectionStateChanged(Vpn::ConnectionState state) emit connectionStateChanged(); } +void ConnectionController::onCurrentContainerChanged() +{ + if(m_isConnected || m_isConnectionInProgress) { + emit reconnectWithChangedContainer(tr("Settings updated successfully, Reconnnection...")); + openConnection(); + } +} + QString ConnectionController::connectionStateText() const { return m_connectionStateText; diff --git a/client/ui/controllers/connectionController.h b/client/ui/controllers/connectionController.h index 5a35f9d8..5ee7a4ca 100644 --- a/client/ui/controllers/connectionController.h +++ b/client/ui/controllers/connectionController.h @@ -32,6 +32,8 @@ public slots: QString getLastConnectionError(); void onConnectionStateChanged(Vpn::ConnectionState state); + void onCurrentContainerChanged(); + signals: void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig); @@ -39,6 +41,7 @@ signals: void connectionStateChanged(); void connectionErrorOccurred(const QString &errorMessage); + void reconnectWithChangedContainer(const QString &message); private: QSharedPointer m_serversModel; diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index c0e9acbb..2d82a4c2 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -254,12 +254,26 @@ void InstallController::updateContainer(QJsonObject config) ServerController serverController(m_settings); connect(&serverController, &ServerController::serverIsBusy, this, &InstallController::serverIsBusy); - auto errorCode = serverController.updateContainer(serverCredentials, container, oldContainerConfig, config); + bool reinstallRequired = serverController.isReinstallContainerRequired(container, oldContainerConfig, config); + auto errorCode = serverController.updateContainer(reinstallRequired, serverCredentials, container, oldContainerConfig, config); if (errorCode == ErrorCode::NoError) { m_containersModel->setData(modelIndex, config, ContainersModel::Roles::ConfigRole); m_protocolModel->updateModel(config); - emit updateContainerFinished(); + bool isCurrentContainerChanged = false; + if (reinstallRequired && + (serverIndex == m_serversModel->getDefaultServerIndex()) && + (container == m_containersModel->getDefaultContainer()) ) { + isCurrentContainerChanged = true; + } + + + if (isCurrentContainerChanged) { + emit currentContainerChanged(); + } else { + emit updateContainerFinished(tr("Settings updated successfully")); + } + return; } diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index 47fc5dab..fc924d72 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -49,7 +49,7 @@ signals: void installContainerFinished(const QString &finishMessage, bool isServiceInstall); void installServerFinished(const QString &finishMessage); - void updateContainerFinished(); + void updateContainerFinished(const QString& message); void scanServerFinished(bool isInstalledContainerFound); @@ -66,6 +66,8 @@ signals: void serverIsBusy(const bool isBusy); + void currentContainerChanged(); + private: void installServer(DockerContainer container, QJsonObject &config); void installContainer(DockerContainer container, QJsonObject &config); diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml index e15c5ec7..1dd9af71 100644 --- a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -13,11 +13,19 @@ import "../Components" PageType { id: root + Connections { + target: ConnectionController + + function onReconnectWithChangedContainer(message) { + PageController.showNotificationMessage(message) + } + } + Connections { target: InstallController - function onUpdateContainerFinished() { - PageController.showNotificationMessage(qsTr("Settings updated successfully")) + function onUpdateContainerFinished(message) { + PageController.showNotificationMessage(message) } } diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index aed1dbc1..741655ea 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -15,11 +15,19 @@ import "../Components" PageType { id: root + Connections { + target: ConnectionController + + function onReconnectWithChangedContainer(message) { + PageController.showNotificationMessage(message) + } + } + Connections { target: InstallController - function onUpdateContainerFinished() { - PageController.showNotificationMessage(qsTr("Settings updated successfully")) + function onUpdateContainerFinished(message) { + PageController.showNotificationMessage(message) } } diff --git a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml index fe0ef8c3..f168efc1 100644 --- a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml @@ -13,11 +13,20 @@ import "../Components" PageType { id: root + + Connections { + target: ConnectionController + + function onReconnectWithChangedContainer(message) { + PageController.showNotificationMessage(message) + } + } + Connections { target: InstallController - function onUpdateContainerFinished() { - PageController.showNotificationMessage(qsTr("Settings updated successfully")) + function onUpdateContainerFinished(message) { + PageController.showNotificationMessage(message) } } From 2b3383a1639b7f1d0c5e04e6b02818c858fb038b Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 14 Sep 2023 15:21:35 +0500 Subject: [PATCH 118/278] removed the transition animation between tabs in the main menu - fixed Drawer freezing when importing files from outside the application --- client/amnezia_application.cpp | 27 +++++++++++++++------------ client/ui/qml/Pages2/PageStart.qml | 4 ++-- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index de75db85..efdb9221 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -105,23 +105,26 @@ void AmneziaApplication::init() return; } - connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, m_importController.get(), - &ImportController::extractConfigFromData); - connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, m_pageController.get(), - &PageController::goToPageViewConfig); + connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, [this](QString data) { + m_pageController->replaceStartPage(); + m_importController->extractConfigFromData(data); + m_pageController->goToPageViewConfig(); + }); #endif #ifdef Q_OS_IOS IosController::Instance()->initialize(); - connect(IosController::Instance(), &IosController::importConfigFromOutside, m_importController.get(), - &ImportController::extractConfigFromData); - connect(IosController::Instance(), &IosController::importConfigFromOutside, m_pageController.get(), - &PageController::goToPageViewConfig); + connect(IosController::Instance(), &IosController::importConfigFromOutside, [this](QString data) { + m_pageController->replaceStartPage(); + m_importController->extractConfigFromData(data); + m_pageController->goToPageViewConfig(); + }); - connect(IosController::Instance(), &IosController::importBackupFromOutside, m_pageController.get(), - &PageController::goToPageSettingsBackup); - connect(IosController::Instance(), &IosController::importBackupFromOutside, m_settingsController.get(), - &SettingsController::importBackupFromOutside); + connect(IosController::Instance(), &IosController::importBackupFromOutside, [this](QString filePath) { + m_pageController->replaceStartPage(); + m_pageController->goToPageSettingsBackup(); + m_settingsController->importBackupFromOutside(filePath); + }); #endif m_notificationHandler.reset(NotificationHandler::create(nullptr)); diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 31474c12..7f9cb212 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -97,8 +97,8 @@ PageType { function goToTabBarPage(page) { var pagePath = PageController.getPagePath(page) - tabBarStackView.clear(StackView.PopTransition) - tabBarStackView.replace(pagePath, { "objectName" : pagePath }) + tabBarStackView.clear(StackView.Immediate) + tabBarStackView.replace(pagePath, { "objectName" : pagePath }, StackView.Immediate) } Component.onCompleted: { From 2fd25f53cca13e796183703d05c18c45e1f13445 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 14 Sep 2023 15:35:24 +0500 Subject: [PATCH 119/278] fixed auto-connection starting after starting the application --- client/amnezia_application.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index efdb9221..0d90cc5a 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -353,6 +353,9 @@ void AmneziaApplication::initControllers() m_settingsController.reset(new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_settings)); m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get()); + if (m_settingsController->isAutoStartEnabled() && m_serversModel->getDefaultServerIndex() >= 0) { + QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); }); + } m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel)); m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get()); From 8a3bdf136bb8e0d30f9d3e363b987ca98b0a313f Mon Sep 17 00:00:00 2001 From: ronoaer Date: Sat, 16 Sep 2023 08:05:43 +0800 Subject: [PATCH 120/278] added close button when drawer shown in the topright corner --- client/resources.qrc | 1 + client/ui/controllers/pageController.cpp | 30 ++++++++++++++++ client/ui/controllers/pageController.h | 11 ++++++ client/ui/qml/Controls2/DrawerType.qml | 21 +++++++++++ .../ui/qml/Controls2/TopCloseButtonType.qml | 35 +++++++++++++++++++ client/ui/qml/Pages2/PageShare.qml | 6 ++++ client/ui/qml/Pages2/PageStart.qml | 20 +++++++++++ 7 files changed, 124 insertions(+) create mode 100644 client/ui/qml/Controls2/TopCloseButtonType.qml diff --git a/client/resources.qrc b/client/resources.qrc index d0ff176f..16372e07 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -213,5 +213,6 @@ images/controls/trash.svg images/controls/more-vertical.svg ui/qml/Controls2/ListViewWithLabelsType.qml + ui/qml/Controls2/TopCloseButtonType.qml diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/pageController.cpp index 46a1b1fd..6501aea8 100644 --- a/client/ui/controllers/pageController.cpp +++ b/client/ui/controllers/pageController.cpp @@ -115,3 +115,33 @@ void PageController::showOnStartup() #endif } } + +void PageController::updateDrawerRootPage(PageLoader::PageEnum page) +{ + m_drwaerLayer = 0; + m_currentRootPage = page; +} + +void PageController::goToDrawerRootPage() +{ + + m_drwaerLayer = 0; + + emit showTopCloseButton(false); + emit forceCloseDrawer(); +} + +void PageController::drawerOpen() +{ + m_drwaerLayer = m_drwaerLayer + 1; + emit showTopCloseButton(true); +} + +void PageController::drawerClose() +{ + m_drwaerLayer = m_drwaerLayer -1; + if (m_drwaerLayer <= 0) { + emit showTopCloseButton(false); + m_drwaerLayer = 0; + } +} diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 07e77283..f4cac427 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -78,6 +78,11 @@ public slots: void showOnStartup(); + void updateDrawerRootPage(PageLoader::PageEnum page); + void goToDrawerRootPage(); + void drawerOpen(); + void drawerClose(); + signals: void goToPage(PageLoader::PageEnum page, bool slide = true); void goToStartPage(); @@ -104,10 +109,16 @@ signals: void showPassphraseRequestDrawer(); void passphraseRequestDrawerClosed(QString passphrase); + void showTopCloseButton(bool visible); + void forceCloseDrawer(); + private: QSharedPointer m_serversModel; std::shared_ptr m_settings; + + PageLoader::PageEnum m_currentRootPage; + int m_drwaerLayer; }; #endif // PAGECONTROLLER_H diff --git a/client/ui/qml/Controls2/DrawerType.qml b/client/ui/qml/Controls2/DrawerType.qml index 35d03449..2b70ef3c 100644 --- a/client/ui/qml/Controls2/DrawerType.qml +++ b/client/ui/qml/Controls2/DrawerType.qml @@ -2,6 +2,16 @@ import QtQuick import QtQuick.Controls Drawer { + property bool needCloseButton: true + + Connections { + target: PageController + + function onForceCloseDrawer() { + visible = false + } + } + edge: Qt.BottomEdge clip: true @@ -40,7 +50,18 @@ Drawer { } } + onOpened: { + if (needCloseButton) { + PageController.drawerOpen() + } + } + + onClosed: { + if (needCloseButton) { + PageController.drawerClose() + } + var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor() if (initialPageNavigationBarColor !== 0xFF1C1D21) { PageController.updateNavigationBarColor(initialPageNavigationBarColor) diff --git a/client/ui/qml/Controls2/TopCloseButtonType.qml b/client/ui/qml/Controls2/TopCloseButtonType.qml new file mode 100644 index 00000000..cd1406c4 --- /dev/null +++ b/client/ui/qml/Controls2/TopCloseButtonType.qml @@ -0,0 +1,35 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Shapes + +Popup { + id: root + + modal: false + closePolicy: Popup.NoAutoClose + width: 40 + height: 40 + padding: 4 + + visible: false + + Overlay.modal: Rectangle { + color: Qt.rgba(14/255, 14/255, 17/255, 0.8) + } + + background: Rectangle { + color: "transparent" + } + + ImageButtonType { + image: "qrc:/images/svg/close_black_24dp.svg" + imageColor: "#D7D8DB" + + implicitWidth: 32 + implicitHeight: 32 + + onClicked: { + PageController.goToDrawerRootPage() + } + } +} diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 135c7fbc..623ceebf 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -27,6 +27,8 @@ PageType { target: ExportController function onGenerateConfig(type) { + shareConnectionDrawer.needCloseButton = false + shareConnectionDrawer.open() shareConnectionDrawer.contentVisible = false PageController.showBusyIndicator(true) @@ -58,6 +60,10 @@ PageType { } PageController.showBusyIndicator(false) + + shareConnectionDrawer.needCloseButton = true + PageController.showTopCloseButton(true) + shareConnectionDrawer.contentVisible = true } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 7f9cb212..e497c455 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -19,16 +19,22 @@ PageType { function onGoToPageHome() { tabBar.currentIndex = 0 tabBarStackView.goToTabBarPage(PageEnum.PageHome) + + PageController.updateDrawerRootPage(PageEnum.PageHome) } function onGoToPageSettings() { tabBar.currentIndex = 2 tabBarStackView.goToTabBarPage(PageEnum.PageSettings) + + PageController.updateDrawerRootPage(PageEnum.PageSettings) } function onGoToPageViewConfig() { var pagePath = PageController.getPagePath(PageEnum.PageSetupWizardViewConfig) tabBarStackView.push(pagePath, { "objectName" : pagePath }, StackView.PushTransition) + + PageController.updateDrawerRootPage(PageEnum.PageSetupWizardViewConfig) } function onShowBusyIndicator(visible) { @@ -37,6 +43,10 @@ PageType { tabBar.enabled = !visible } + function onShowTopCloseButton(visible) { + topCloseButton.visible = visible + } + function onEnableTabBar(enabled) { tabBar.enabled = enabled } @@ -55,6 +65,8 @@ PageType { } else { tabBarStackView.push(pagePath, { "objectName" : pagePath }, StackView.Immediate) } + + PageController.updateDrawerRootPage(page) } function onGoToStartPage() { @@ -99,6 +111,8 @@ PageType { var pagePath = PageController.getPagePath(page) tabBarStackView.clear(StackView.Immediate) tabBarStackView.replace(pagePath, { "objectName" : pagePath }, StackView.Immediate) + + PageController.updateDrawerRootPage(page) } Component.onCompleted: { @@ -183,4 +197,10 @@ PageType { anchors.centerIn: parent z: 1 } + + TopCloseButtonType { + id: topCloseButton + x: tabBarStackView.width - topCloseButton.width + z: 1 + } } From 9eebee3ce38395c6b6e274fda2cfc828150194b2 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Sat, 16 Sep 2023 16:20:19 +0800 Subject: [PATCH 121/278] fixed additional info can not be save in page OpenVpn settings --- client/ui/qml/Controls2/TextAreaType.qml | 9 ++------ .../Pages2/PageProtocolOpenVpnSettings.qml | 22 ++++++++++++------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/client/ui/qml/Controls2/TextAreaType.qml b/client/ui/qml/Controls2/TextAreaType.qml index a75ea55d..a7e2fee7 100644 --- a/client/ui/qml/Controls2/TextAreaType.qml +++ b/client/ui/qml/Controls2/TextAreaType.qml @@ -6,7 +6,8 @@ Rectangle { property string placeholderText property string text - property var onEditingFinished + property alias textArea: textArea + property alias textAreaText: textArea.text height: 148 color: "#1C1D21" @@ -40,12 +41,6 @@ Rectangle { placeholderText: root.placeholderText text: root.text - onEditingFinished: { - if (root.onEditingFinished && typeof root.onEditingFinished === "function") { - root.onEditingFinished() - } - } - wrapMode: Text.Wrap MouseArea { diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index aed1dbc1..acb31327 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -312,12 +312,12 @@ PageType { visible: additionalClientCommandsSwitcher.checked - text: additionalClientCommands + textAreaText: additionalClientCommands placeholderText: qsTr("Commands:") - onEditingFinished: { - if (additionalClientCommands !== text) { - additionalClientCommands = text + textArea.onEditingFinished: { + if (additionalClientCommands !== textAreaText) { + additionalClientCommands = textAreaText } } } @@ -330,6 +330,12 @@ PageType { checked: additionalServerCommands !== "" text: qsTr("Additional server configuration commands") + + onCheckedChanged: { + if (!checked) { + additionalServerCommands = "" + } + } } TextAreaType { @@ -338,12 +344,12 @@ PageType { visible: additionalServerCommandsSwitcher.checked - text: additionalServerCommands + textAreaText: additionalServerCommands placeholderText: qsTr("Commands:") - onEditingFinished: { - if (additionalServerCommands !== text) { - additionalServerCommands = text + textArea.onEditingFinished: { + if (additionalServerCommands !== textAreaText) { + additionalServerCommands = textAreaText } } } From f40bf2d9ba1c015be9cd6ddead57cb32b49da3e4 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sun, 17 Sep 2023 15:01:31 +0500 Subject: [PATCH 122/278] limited the length of the displayed server name - added auto-selection of the first available protocol when changing the server on the PageShare page --- CMakeLists.txt | 4 +- client/ui/qml/Controls2/DropDownType.qml | 4 + client/ui/qml/Controls2/HeaderType.qml | 5 + .../ui/qml/Controls2/LabelWithButtonType.qml | 5 + .../Controls2/ListViewWithRadioButtonType.qml | 26 ++- .../ui/qml/Controls2/VerticalRadioButton.qml | 4 + client/ui/qml/Pages2/PageHome.qml | 15 +- .../ui/qml/Pages2/PageSettingsServerInfo.qml | 2 +- client/ui/qml/Pages2/PageShare.qml | 186 ++++++++---------- 9 files changed, 136 insertions(+), 115 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 49b8fa16..f7ada65a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,11 +2,11 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.0.5.1 +project(${PROJECT} VERSION 4.0.6.1 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) -set(RELEASE_DATE "2023-09-11") +set(RELEASE_DATE "2023-09-17") set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index c21fa48f..2feb5e17 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -10,6 +10,8 @@ Item { property string text property string textColor: "#d7d8db" property string textDisabledColor: "#878B91" + property int textMaximumLineCount: 2 + property int textElide: Qt.ElideRight property string descriptionText property string descriptionTextColor: "#878B91" @@ -102,6 +104,8 @@ Item { color: root.enabled ? root.textColor : root.textDisabledColor text: root.text + maximumLineCount: root.textMaximumLineCount + elide: root.textElide } } diff --git a/client/ui/qml/Controls2/HeaderType.qml b/client/ui/qml/Controls2/HeaderType.qml index 19958205..b4af3784 100644 --- a/client/ui/qml/Controls2/HeaderType.qml +++ b/client/ui/qml/Controls2/HeaderType.qml @@ -10,6 +10,9 @@ Item { property var actionButtonFunction property string headerText + property int headerTextMaximumLineCount: 2 + property int headerTextElide: Qt.ElideRight + property string descriptionText implicitWidth: content.implicitWidth @@ -26,6 +29,8 @@ Item { Layout.fillWidth: true text: root.headerText + maximumLineCount: root.headerTextMaximumLineCount + elide: root.headerTextElide } ImageButtonType { diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml index 82aef55a..7a1489c0 100644 --- a/client/ui/qml/Controls2/LabelWithButtonType.qml +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -8,6 +8,9 @@ Item { id: root property string text + property int textMaximumLineCount: 2 + property int textElide: Qt.ElideRight + property string descriptionText property var clickedFunction @@ -68,6 +71,8 @@ Item { ListItemTitleType { text: root.text color: root.descriptionOnTop ? root.descriptionColor : root.textColor + maximumLineCount: root.textMaximumLineCount + elide: root.textElide opacity: root.textOpacity diff --git a/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml b/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml index f7f99dec..4138c087 100644 --- a/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml +++ b/client/ui/qml/Controls2/ListViewWithRadioButtonType.qml @@ -5,12 +5,15 @@ import QtQuick.Layouts import "TextTypes" ListView { - id: menuContent + id: root property var rootWidth property var selectedText + property int textMaximumLineCount: 2 + property int textElide: Qt.ElideRight + property string imageSource: "qrc:/images/controls/check.svg" property var clickedFunction @@ -18,7 +21,7 @@ ListView { currentIndex: 0 width: rootWidth - height: menuContent.contentItem.height + height: root.contentItem.height clip: true interactive: false @@ -27,6 +30,12 @@ ListView { id: buttonGroup } + function triggerCurrentItem() { + var item = root.itemAtIndex(currentIndex) + var radioButton = item.children[0].children[0] + radioButton.clicked() + } + delegate: Item { implicitWidth: rootWidth implicitHeight: content.implicitHeight @@ -74,6 +83,9 @@ ListView { Layout.bottomMargin: 20 text: name + maximumLineCount: root.textMaximumLineCount + elide: root.textElide + } Image { @@ -88,11 +100,11 @@ ListView { } ButtonGroup.group: buttonGroup - checked: menuContent.currentIndex === index + checked: root.currentIndex === index onClicked: { - menuContent.currentIndex = index - menuContent.selectedText = name + root.currentIndex = index + root.selectedText = name if (clickedFunction && typeof clickedFunction === "function") { clickedFunction() } @@ -101,8 +113,8 @@ ListView { } Component.onCompleted: { - if (menuContent.currentIndex === index) { - menuContent.selectedText = name + if (root.currentIndex === index) { + root.selectedText = name } } } diff --git a/client/ui/qml/Controls2/VerticalRadioButton.qml b/client/ui/qml/Controls2/VerticalRadioButton.qml index 8229c959..dd9a5590 100644 --- a/client/ui/qml/Controls2/VerticalRadioButton.qml +++ b/client/ui/qml/Controls2/VerticalRadioButton.qml @@ -8,6 +8,8 @@ import "TextTypes" RadioButton { id: root + property int textMaximumLineCount: 2 + property int textElide: Qt.ElideRight property string descriptionText property string hoveredColor: Qt.rgba(1, 1, 1, 0.05) @@ -104,6 +106,8 @@ RadioButton { ListItemTitleType { text: root.text + maximumLineCount: root.textMaximumLineCount + elide: root.textElide color: { if (root.checked) { diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index c5ba89d0..d8796524 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -75,12 +75,20 @@ PageType { RowLayout { Layout.topMargin: 24 + Layout.leftMargin: 24 + Layout.rightMargin: 24 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter spacing: 0 Header1TextType { + Layout.maximumWidth: buttonContent.width - 48 - 18 - 12 // todo + + maximumLineCount: 2 + elide: Qt.ElideRight + text: root.defaultServerName + horizontalAlignment: Qt.AlignHCenter } Image { @@ -148,10 +156,15 @@ PageType { anchors.left: parent.left Header1TextType { + Layout.fillWidth: true Layout.topMargin: 24 - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + Layout.leftMargin: 16 + Layout.rightMargin: 16 text: root.defaultServerName + horizontalAlignment: Qt.AlignHCenter + maximumLineCount: 2 + elide: Qt.ElideRight } LabelTextType { diff --git a/client/ui/qml/Pages2/PageSettingsServerInfo.qml b/client/ui/qml/Pages2/PageSettingsServerInfo.qml index 62a7a67b..e2e7868c 100644 --- a/client/ui/qml/Pages2/PageSettingsServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsServerInfo.qml @@ -101,7 +101,7 @@ PageType { Layout.fillWidth: true headerText: qsTr("Server name") textFieldText: name - textField.maximumLength: 20 + textField.maximumLength: 30 } BasicButtonType { diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 135c7fbc..67083949 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -173,19 +173,22 @@ PageType { DropDownType { id: serverSelector + signal severSelectorIndexChanged + property int currentIndex: 0 + Layout.fillWidth: true Layout.topMargin: 16 drawerHeight: 0.4375 - descriptionText: accessTypeSelector.currentIndex === 0 ? qsTr("Server and service") : qsTr("Server") + descriptionText: qsTr("Servers") headerText: qsTr("Server") - listView: ListViewWithLabelsType { - rootWidth: root.width - dividerVisible: true + listView: ListViewWithRadioButtonType { + id: serverSelectorListView - imageSource: "qrc:/images/controls/chevron-right.svg" + rootWidth: root.width + imageSource: "qrc:/images/controls/check.svg" model: SortFilterProxyModel { id: proxyServersModel @@ -203,14 +206,16 @@ PageType { clickedFunction: function() { handler() - if (accessTypeSelector.currentIndex === 0) { - protocolSelector.visible = true - root.shareButtonEnabled = false - } else { + if (serverSelector.currentIndex !== serverSelectorListView.currentIndex) { + serverSelector.currentIndex = serverSelectorListView.currentIndex + serverSelector.severSelectorIndexChanged() + } + + if (accessTypeSelector.currentIndex !== 0) { shareConnectionDrawer.headerText = qsTr("Accessing ") + serverSelector.text shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text - serverSelector.menuVisible = false } + serverSelector.menuVisible = false } Component.onCompleted: { @@ -224,117 +229,90 @@ PageType { ServersModel.currentlyProcessedIndex = proxyServersModel.mapToSource(currentIndex) } } + } - DrawerType { - id: protocolSelector + DropDownType { + id: protocolSelector - width: parent.width - height: parent.height * 0.5 + Layout.fillWidth: true + Layout.topMargin: 16 - ColumnLayout { - id: protocolSelectorHeader + drawerHeight: 0.5 - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.topMargin: 16 + descriptionText: qsTr("Protocols") + headerText: qsTr("Protocol") - BackButtonType { - backButtonImage: "qrc:/images/controls/arrow-left.svg" - backButtonFunction: function() { - protocolSelector.visible = false + listView: ListViewWithRadioButtonType { + id: protocolSelectorListView + + rootWidth: root.width + imageSource: "qrc:/images/controls/check.svg" + + model: SortFilterProxyModel { + id: proxyContainersModel + sourceModel: ContainersModel + filters: [ + ValueFilter { + roleName: "isInstalled" + value: true + }, + ValueFilter { + roleName: "isShareable" + value: true } + ] + } + + currentIndex: 0 + + clickedFunction: function() { + handler() + + protocolSelector.menuVisible = false + } + + Component.onCompleted: { + if (accessTypeSelector.currentIndex === 0) { + handler() } } - FlickableType { - anchors.top: protocolSelectorHeader.bottom - anchors.topMargin: 16 - contentHeight: protocolSelectorContent.implicitHeight + Connections { + target: serverSelector - Column { - id: protocolSelectorContent - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right + function onSeverSelectorIndexChanged() { + protocolSelectorListView.currentIndex = 0 + protocolSelectorListView.triggerCurrentItem() + } + } - spacing: 16 + function handler() { + if (!proxyContainersModel.count) { + root.shareButtonEnabled = false + return + } else { + root.shareButtonEnabled = true + } - Header2TextType { - anchors.left: parent.left - anchors.right: parent.right - anchors.leftMargin: 16 - anchors.rightMargin: 16 + protocolSelector.text = selectedText + root.connectionServerSelectorText = serverSelector.text - text: qsTr("Protocols and services") - wrapMode: Text.WordWrap - } + shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text + shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text + ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(currentIndex)) - ListViewWithRadioButtonType { - rootWidth: root.width + fillConnectionTypeModel() + } - imageSource: "qrc:/images/controls/check.svg" + function fillConnectionTypeModel() { + root.connectionTypesModel = [amneziaConnectionFormat] - model: SortFilterProxyModel { - id: proxyContainersModel - sourceModel: ContainersModel - filters: [ - ValueFilter { - roleName: "isInstalled" - value: true - }, - ValueFilter { - roleName: "isShareable" - value: true - } - ] - } + var index = proxyContainersModel.mapToSource(currentIndex) - currentIndex: 0 - - clickedFunction: function() { - handler() - - protocolSelector.visible = false - serverSelector.menuVisible = false - } - - Component.onCompleted: { - if (accessTypeSelector.currentIndex === 0) { - handler() - } - } - - function handler() { - if (!proxyContainersModel.count) { - root.shareButtonEnabled = false - return - } else { - root.shareButtonEnabled = true - } - - serverSelector.text += ", " + selectedText - root.connectionServerSelectorText = serverSelector.text - - shareConnectionDrawer.headerText = qsTr("Connection to ") + serverSelector.text - shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text - ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(currentIndex)) - - fillConnectionTypeModel() - } - - function fillConnectionTypeModel() { - root.connectionTypesModel = [amneziaConnectionFormat] - - var index = proxyContainersModel.mapToSource(currentIndex) - - if (index === ContainerProps.containerFromString("amnezia-openvpn")) { - root.connectionTypesModel.push(openVpnConnectionFormat) - } else if (index === ContainerProps.containerFromString("amnezia-wireguard")) { - root.connectionTypesModel.push(wireGuardConnectionFormat) - } - } - } + if (index === ContainerProps.containerFromString("amnezia-openvpn")) { + root.connectionTypesModel.push(openVpnConnectionFormat) + } else if (index === ContainerProps.containerFromString("amnezia-wireguard")) { + root.connectionTypesModel.push(wireGuardConnectionFormat) } } } From c0cb5b96bfc91e5023e2bd06defb66af9deb4c1b Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sun, 17 Sep 2023 17:03:39 +0500 Subject: [PATCH 123/278] added reconnection to vpn after changing any protocol settings --- client/amnezia_application.cpp | 4 ++-- client/core/servercontroller.cpp | 8 +++++--- client/core/servercontroller.h | 11 ++++------- client/ui/controllers/connectionController.cpp | 6 +++--- client/ui/controllers/connectionController.h | 4 ++-- client/ui/controllers/installController.cpp | 16 ++++------------ client/ui/controllers/installController.h | 2 +- .../ui/qml/Pages2/PageProtocolCloakSettings.qml | 16 ---------------- .../qml/Pages2/PageProtocolOpenVpnSettings.qml | 16 ---------------- .../Pages2/PageProtocolShadowSocksSettings.qml | 17 ----------------- client/ui/qml/Pages2/PageStart.qml | 12 ++++++++++++ 11 files changed, 33 insertions(+), 79 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index cdcee5f4..37f4d68b 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -336,8 +336,8 @@ void AmneziaApplication::initControllers() &PageController::showPassphraseRequestDrawer); connect(m_pageController.get(), &PageController::passphraseRequestDrawerClosed, m_installController.get(), &InstallController::setEncryptedPassphrase); - connect(m_installController.get(), &InstallController::currentContainerChanged, m_connectionController.get(), - &ConnectionController::onCurrentContainerChanged); + connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(), + &ConnectionController::onCurrentContainerUpdated); m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings)); m_engine->rootContext()->setContextProperty("ImportController", m_importController.get()); diff --git a/client/core/servercontroller.cpp b/client/core/servercontroller.cpp index a82785eb..b0f8146f 100644 --- a/client/core/servercontroller.cpp +++ b/client/core/servercontroller.cpp @@ -290,11 +290,13 @@ ErrorCode ServerController::setupContainer(const ServerCredentials &credentials, return startupContainerWorker(credentials, container, config); } -ErrorCode ServerController::updateContainer(const bool reinstallRequired, - const ServerCredentials &credentials, - DockerContainer container, +ErrorCode ServerController::updateContainer(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &oldConfig, QJsonObject &newConfig) { + bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig); + qDebug() << "ServerController::updateContainer for container" << container << "reinstall required is" + << reinstallRequired; + if (reinstallRequired) { return setupContainer(credentials, container, newConfig, true); } else { diff --git a/client/core/servercontroller.h b/client/core/servercontroller.h index d3f242a3..3191386c 100644 --- a/client/core/servercontroller.h +++ b/client/core/servercontroller.h @@ -26,10 +26,8 @@ public: ErrorCode removeContainer(const ServerCredentials &credentials, DockerContainer container); ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config, bool isUpdate = false); - ErrorCode updateContainer(const bool reinstallRequired, const ServerCredentials &credentials, - DockerContainer container, - const QJsonObject &oldConfig, - QJsonObject &newConfig); + ErrorCode updateContainer(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &oldConfig, QJsonObject &newConfig); ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials, QMap &installedContainers); @@ -63,8 +61,6 @@ public: ErrorCode getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, const std::function &callback); - bool isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, - const QJsonObject &newConfig); private: ErrorCode installDockerWorker(const ServerCredentials &credentials, DockerContainer container); ErrorCode prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, @@ -77,7 +73,8 @@ private: ErrorCode isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config); - + bool isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, + const QJsonObject &newConfig); ErrorCode isUserInSudo(const ServerCredentials &credentials, DockerContainer container); ErrorCode isServerDpkgBusy(const ServerCredentials &credentials, DockerContainer container); diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index 180d96e7..77ca0f5f 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -118,10 +118,10 @@ void ConnectionController::onConnectionStateChanged(Vpn::ConnectionState state) emit connectionStateChanged(); } -void ConnectionController::onCurrentContainerChanged() +void ConnectionController::onCurrentContainerUpdated() { - if(m_isConnected || m_isConnectionInProgress) { - emit reconnectWithChangedContainer(tr("Settings updated successfully, Reconnnection...")); + if (m_isConnected || m_isConnectionInProgress) { + emit reconnectWithUpdatedContainer(tr("Settings updated successfully, Reconnnection...")); openConnection(); } } diff --git a/client/ui/controllers/connectionController.h b/client/ui/controllers/connectionController.h index 5ee7a4ca..7bfe0fac 100644 --- a/client/ui/controllers/connectionController.h +++ b/client/ui/controllers/connectionController.h @@ -32,7 +32,7 @@ public slots: QString getLastConnectionError(); void onConnectionStateChanged(Vpn::ConnectionState state); - void onCurrentContainerChanged(); + void onCurrentContainerUpdated(); signals: void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, @@ -41,7 +41,7 @@ signals: void connectionStateChanged(); void connectionErrorOccurred(const QString &errorMessage); - void reconnectWithChangedContainer(const QString &message); + void reconnectWithUpdatedContainer(const QString &message); private: QSharedPointer m_serversModel; diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 2d82a4c2..63510d1a 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -254,22 +254,14 @@ void InstallController::updateContainer(QJsonObject config) ServerController serverController(m_settings); connect(&serverController, &ServerController::serverIsBusy, this, &InstallController::serverIsBusy); - bool reinstallRequired = serverController.isReinstallContainerRequired(container, oldContainerConfig, config); - auto errorCode = serverController.updateContainer(reinstallRequired, serverCredentials, container, oldContainerConfig, config); + auto errorCode = serverController.updateContainer(serverCredentials, container, oldContainerConfig, config); if (errorCode == ErrorCode::NoError) { m_containersModel->setData(modelIndex, config, ContainersModel::Roles::ConfigRole); m_protocolModel->updateModel(config); - bool isCurrentContainerChanged = false; - if (reinstallRequired && - (serverIndex == m_serversModel->getDefaultServerIndex()) && - (container == m_containersModel->getDefaultContainer()) ) { - isCurrentContainerChanged = true; - } - - - if (isCurrentContainerChanged) { - emit currentContainerChanged(); + if ((serverIndex == m_serversModel->getDefaultServerIndex()) + && (container == m_containersModel->getDefaultContainer())) { + emit currentContainerUpdated(); } else { emit updateContainerFinished(tr("Settings updated successfully")); } diff --git a/client/ui/controllers/installController.h b/client/ui/controllers/installController.h index fc924d72..a5fd2875 100644 --- a/client/ui/controllers/installController.h +++ b/client/ui/controllers/installController.h @@ -66,7 +66,7 @@ signals: void serverIsBusy(const bool isBusy); - void currentContainerChanged(); + void currentContainerUpdated(); private: void installServer(DockerContainer container, QJsonObject &config); diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml index 1dd9af71..78e666a7 100644 --- a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -13,22 +13,6 @@ import "../Components" PageType { id: root - Connections { - target: ConnectionController - - function onReconnectWithChangedContainer(message) { - PageController.showNotificationMessage(message) - } - } - - Connections { - target: InstallController - - function onUpdateContainerFinished(message) { - PageController.showNotificationMessage(message) - } - } - ColumnLayout { id: backButton diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index 741655ea..f5313e16 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -15,22 +15,6 @@ import "../Components" PageType { id: root - Connections { - target: ConnectionController - - function onReconnectWithChangedContainer(message) { - PageController.showNotificationMessage(message) - } - } - - Connections { - target: InstallController - - function onUpdateContainerFinished(message) { - PageController.showNotificationMessage(message) - } - } - ColumnLayout { id: backButton diff --git a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml index f168efc1..2453281f 100644 --- a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml @@ -13,23 +13,6 @@ import "../Components" PageType { id: root - - Connections { - target: ConnectionController - - function onReconnectWithChangedContainer(message) { - PageController.showNotificationMessage(message) - } - } - - Connections { - target: InstallController - - function onUpdateContainerFinished(message) { - PageController.showNotificationMessage(message) - } - } - ColumnLayout { id: backButton diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 31474c12..f7020a2d 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -82,6 +82,18 @@ PageType { PageController.closePage() } } + + function onUpdateContainerFinished(message) { + PageController.showNotificationMessage(message) + } + } + + Connections { + target: ConnectionController + + function onReconnectWithUpdatedContainer(message) { + PageController.showNotificationMessage(message) + } } StackViewType { From 8965b1fbba6debeb53195c1791e83490d5c353bb Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sun, 17 Sep 2023 23:21:00 +0500 Subject: [PATCH 124/278] fixed the size of the drawer close button --- client/ui/qml/Controls2/DrawerType.qml | 7 +++---- client/ui/qml/Controls2/TopCloseButtonType.qml | 5 ----- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/client/ui/qml/Controls2/DrawerType.qml b/client/ui/qml/Controls2/DrawerType.qml index 2b70ef3c..34b141b4 100644 --- a/client/ui/qml/Controls2/DrawerType.qml +++ b/client/ui/qml/Controls2/DrawerType.qml @@ -48,20 +48,19 @@ Drawer { if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) { PageController.updateNavigationBarColor(0xFF1C1D21) } - } - onOpened: { if (needCloseButton) { PageController.drawerOpen() } } - - onClosed: { + onAboutToHide: { if (needCloseButton) { PageController.drawerClose() } + } + onClosed: { var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor() if (initialPageNavigationBarColor !== 0xFF1C1D21) { PageController.updateNavigationBarColor(initialPageNavigationBarColor) diff --git a/client/ui/qml/Controls2/TopCloseButtonType.qml b/client/ui/qml/Controls2/TopCloseButtonType.qml index cd1406c4..ed89b5a6 100644 --- a/client/ui/qml/Controls2/TopCloseButtonType.qml +++ b/client/ui/qml/Controls2/TopCloseButtonType.qml @@ -7,8 +7,6 @@ Popup { modal: false closePolicy: Popup.NoAutoClose - width: 40 - height: 40 padding: 4 visible: false @@ -25,9 +23,6 @@ Popup { image: "qrc:/images/svg/close_black_24dp.svg" imageColor: "#D7D8DB" - implicitWidth: 32 - implicitHeight: 32 - onClicked: { PageController.goToDrawerRootPage() } From ad236baa8686b12f7330df52a8cea4ecb9ef620b Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sun, 17 Sep 2023 23:24:21 +0500 Subject: [PATCH 125/278] fixed a typo in the variable name --- client/ui/controllers/pageController.cpp | 12 ++++++------ client/ui/controllers/pageController.h | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/pageController.cpp index 6501aea8..7b8f74ab 100644 --- a/client/ui/controllers/pageController.cpp +++ b/client/ui/controllers/pageController.cpp @@ -118,14 +118,14 @@ void PageController::showOnStartup() void PageController::updateDrawerRootPage(PageLoader::PageEnum page) { - m_drwaerLayer = 0; + m_drawerLayer = 0; m_currentRootPage = page; } void PageController::goToDrawerRootPage() { - m_drwaerLayer = 0; + m_drawerLayer = 0; emit showTopCloseButton(false); emit forceCloseDrawer(); @@ -133,15 +133,15 @@ void PageController::goToDrawerRootPage() void PageController::drawerOpen() { - m_drwaerLayer = m_drwaerLayer + 1; + m_drawerLayer = m_drawerLayer + 1; emit showTopCloseButton(true); } void PageController::drawerClose() { - m_drwaerLayer = m_drwaerLayer -1; - if (m_drwaerLayer <= 0) { + m_drawerLayer = m_drawerLayer -1; + if (m_drawerLayer <= 0) { emit showTopCloseButton(false); - m_drwaerLayer = 0; + m_drawerLayer = 0; } } diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index f4cac427..e047e6d6 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -118,7 +118,7 @@ private: std::shared_ptr m_settings; PageLoader::PageEnum m_currentRootPage; - int m_drwaerLayer; + int m_drawerLayer; }; #endif // PAGECONTROLLER_H From fd09321f8e71ff68217e5108c0003476146ffb00 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 18 Sep 2023 00:16:58 +0500 Subject: [PATCH 126/278] removed the 'mount sftp folder' button for mobile platforms --- client/ui/qml/Pages2/PageServiceSftpSettings.qml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/ui/qml/Pages2/PageServiceSftpSettings.qml b/client/ui/qml/Pages2/PageServiceSftpSettings.qml index 9287bd20..61ba663d 100644 --- a/client/ui/qml/Pages2/PageServiceSftpSettings.qml +++ b/client/ui/qml/Pages2/PageServiceSftpSettings.qml @@ -165,6 +165,8 @@ PageType { } BasicButtonType { + visible: !GC.isMobile() + Layout.fillWidth: true Layout.topMargin: 24 Layout.bottomMargin: 24 From f7370a0280d44e9367c03b1c8d8d0d259ca298d2 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Mon, 18 Sep 2023 14:35:22 +0800 Subject: [PATCH 127/278] fixed: topright-corner button not visible when drawer closed --- client/ui/qml/Controls2/DrawerType.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ui/qml/Controls2/DrawerType.qml b/client/ui/qml/Controls2/DrawerType.qml index 34b141b4..1c4c508f 100644 --- a/client/ui/qml/Controls2/DrawerType.qml +++ b/client/ui/qml/Controls2/DrawerType.qml @@ -48,19 +48,19 @@ Drawer { if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) { PageController.updateNavigationBarColor(0xFF1C1D21) } + } + onOpened: { if (needCloseButton) { PageController.drawerOpen() } } - onAboutToHide: { + onClosed: { if (needCloseButton) { PageController.drawerClose() } - } - onClosed: { var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor() if (initialPageNavigationBarColor !== 0xFF1C1D21) { PageController.updateNavigationBarColor(initialPageNavigationBarColor) From 9e7cf3ccd93eff6dadadb3efa46c3a5a7e5b967a Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 18 Sep 2023 16:39:26 +0500 Subject: [PATCH 128/278] added PageServiceDnsSettings --- client/resources.qrc | 1 + client/ui/controllers/pageController.h | 1 + .../Components/SettingsContainersListView.qml | 23 +++-- .../ui/qml/Pages2/PageServiceDnsSettings.qml | 95 +++++++++++++++++++ 4 files changed, 110 insertions(+), 10 deletions(-) create mode 100644 client/ui/qml/Pages2/PageServiceDnsSettings.qml diff --git a/client/resources.qrc b/client/resources.qrc index d0ff176f..5b919cf4 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -213,5 +213,6 @@ images/controls/trash.svg images/controls/more-vertical.svg ui/qml/Controls2/ListViewWithLabelsType.qml + ui/qml/Pages2/PageServiceDnsSettings.qml diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 07e77283..508a9d58 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -32,6 +32,7 @@ namespace PageLoader PageServiceSftpSettings, PageServiceTorWebsiteSettings, + PageServiceDnsSettings, PageSetupWizardStart, PageSetupWizardCredentials, diff --git a/client/ui/qml/Components/SettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml index a0c74a04..edd96bd7 100644 --- a/client/ui/qml/Components/SettingsContainersListView.qml +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -43,10 +43,12 @@ ListView { var containerIndex = root.model.mapToSource(index) ContainersModel.setCurrentlyProcessedContainerIndex(containerIndex) - if (config[ContainerProps.containerTypeToString(containerIndex)]["isThirdPartyConfig"]) { - ProtocolsModel.updateModel(config) - PageController.goToPage(PageEnum.PageProtocolRaw) - return + if (serviceType !== ProtocolEnum.Other) { + if (config[ContainerProps.containerTypeToString(containerIndex)]["isThirdPartyConfig"]) { + ProtocolsModel.updateModel(config) + PageController.goToPage(PageEnum.PageProtocolRaw) + return + } } switch (containerIndex) { @@ -78,12 +80,13 @@ ListView { PageController.goToPage(PageEnum.PageServiceTorWebsiteSettings) break } - - default: { - if (serviceType !== ProtocolEnum.Other) { //todo disable settings for dns container - ProtocolsModel.updateModel(config) - PageController.goToPage(PageEnum.PageSettingsServerProtocol) - } + case ContainerEnum.Dns: { + PageController.goToPage(PageEnum.PageServiceDnsSettings) + break + } + default: { // go to the settings page of the container with multiple protocols + ProtocolsModel.updateModel(config) + PageController.goToPage(PageEnum.PageSettingsServerProtocol) } } diff --git a/client/ui/qml/Pages2/PageServiceDnsSettings.qml b/client/ui/qml/Pages2/PageServiceDnsSettings.qml new file mode 100644 index 00000000..016a7c88 --- /dev/null +++ b/client/ui/qml/Pages2/PageServiceDnsSettings.qml @@ -0,0 +1,95 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + ColumnLayout { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + } + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.implicitHeight + + ColumnLayout { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + HeaderType { + id: header + + Layout.fillWidth: true + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + headerText: "Amnezia DNS" + descriptionText: qsTr("A DNS service is installed on your server, and it is only accessible via VPN.\n") + + qsTr("The DNS address is the same as the address of your server. You can configure DNS in the settings, under the connections tab.") + } + + LabelWithButtonType { + id: removeButton + + Layout.topMargin: 24 + width: parent.width + + text: qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() + textColor: "#EB5757" + + clickedFunction: function() { + questionDrawer.headerText = qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() + qsTr(" from server?") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + PageController.goToPage(PageEnum.PageDeinstalling) + InstallController.removeCurrentlyProcessedContainer() + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true + } + + MouseArea { + anchors.fill: removeButton + cursorShape: Qt.PointingHandCursor + enabled: false + } + } + + DividerType {} + + QuestionDrawer { + id: questionDrawer + } + } + } +} From 152d7bc3b3d0ed3fd63d201a57e3161543f3c3a5 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 18 Sep 2023 17:52:41 +0500 Subject: [PATCH 129/278] added restore default settings for dns settings page --- client/ui/qml/Pages2/PageSettingsDns.qml | 37 ++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/client/ui/qml/Pages2/PageSettingsDns.qml b/client/ui/qml/Pages2/PageSettingsDns.qml index 2851e9b1..0bc13eae 100644 --- a/client/ui/qml/Pages2/PageSettingsDns.qml +++ b/client/ui/qml/Pages2/PageSettingsDns.qml @@ -8,6 +8,7 @@ import "./" import "../Controls2" import "../Config" import "../Controls2/TextTypes" +import "../Components" PageType { id: root @@ -72,6 +73,38 @@ PageType { } } + BasicButtonType { + Layout.fillWidth: true + + 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("Restore default") + + onClicked: function() { + questionDrawer.headerText = qsTr("Restore default DNS settings?") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + SettingsController.primaryDns = "1.1.1.1" + primaryDns.textFieldText = SettingsController.primaryDns + SettingsController.secondaryDns = "1.0.0.1" + secondaryDns.textFieldText = SettingsController.secondaryDns + PageController.showNotificationMessage(qsTr("Settings have been reset")) + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true + } + } + BasicButtonType { Layout.fillWidth: true @@ -84,8 +117,12 @@ PageType { if (secondaryDns.textFieldText !== SettingsController.secondaryDns) { SettingsController.secondaryDns = secondaryDns.textFieldText } + PageController.showNotificationMessage(qsTr("Settings saved")) } } } + QuestionDrawer { + id: questionDrawer + } } } From d4d6fbab882124770b8065865c995df1ed06801c Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 18 Sep 2023 21:06:10 +0500 Subject: [PATCH 130/278] changed the protocols for easySetup setup --- client/containers/containers_defs.cpp | 22 ++++++++++++++------ client/containers/containers_defs.h | 1 + client/ui/models/containers_model.cpp | 7 ++----- client/ui/models/containers_model.h | 1 + client/ui/qml/Pages2/PageSetupWizardEasy.qml | 2 +- 5 files changed, 21 insertions(+), 12 deletions(-) diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index 766d89da..20fc59f4 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -223,9 +223,9 @@ QStringList ContainerProps::fixedPortsForContainer(DockerContainer c) bool ContainerProps::isEasySetupContainer(DockerContainer container) { switch (container) { - case DockerContainer::OpenVpn: return true; + case DockerContainer::WireGuard: return true; case DockerContainer::Cloak: return true; - case DockerContainer::ShadowSocks: return true; + case DockerContainer::OpenVpn: return true; default: return false; } } @@ -233,9 +233,9 @@ bool ContainerProps::isEasySetupContainer(DockerContainer container) QString ContainerProps::easySetupHeader(DockerContainer container) { switch (container) { - case DockerContainer::OpenVpn: return tr("Low"); + case DockerContainer::WireGuard: return tr("Low"); case DockerContainer::Cloak: return tr("High"); - case DockerContainer::ShadowSocks: return tr("Medium"); + case DockerContainer::OpenVpn: return tr("Medium"); default: return ""; } } @@ -243,13 +243,23 @@ QString ContainerProps::easySetupHeader(DockerContainer container) QString ContainerProps::easySetupDescription(DockerContainer container) { switch (container) { - case DockerContainer::OpenVpn: return tr("I just want to increase the level of privacy"); + case DockerContainer::WireGuard: return tr("I just want to increase the level of privacy"); case DockerContainer::Cloak: return tr("Many foreign websites and VPN providers are blocked"); - case DockerContainer::ShadowSocks: return tr("Some foreign sites are blocked, but VPN providers are not blocked"); + case DockerContainer::OpenVpn: return tr("Some foreign sites are blocked, but VPN providers are not blocked"); default: return ""; } } +int ContainerProps::easySetupOrder(DockerContainer container) +{ + switch (container) { + case DockerContainer::WireGuard: return 1; + case DockerContainer::Cloak: return 3; + case DockerContainer::OpenVpn: return 2; + default: return 0; + } +} + bool ContainerProps::isShareable(DockerContainer container) { switch (container) { diff --git a/client/containers/containers_defs.h b/client/containers/containers_defs.h index 24782407..9ca51a96 100644 --- a/client/containers/containers_defs.h +++ b/client/containers/containers_defs.h @@ -64,6 +64,7 @@ namespace amnezia static bool isEasySetupContainer(amnezia::DockerContainer container); static QString easySetupHeader(amnezia::DockerContainer container); static QString easySetupDescription(amnezia::DockerContainer container); + static int easySetupOrder(amnezia::DockerContainer container); static bool isShareable(amnezia::DockerContainer container); }; diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index 67847727..afff44b6 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -75,6 +75,7 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const case IsEasySetupContainerRole: return ContainerProps::isEasySetupContainer(container); case EasySetupHeaderRole: return ContainerProps::easySetupHeader(container); case EasySetupDescriptionRole: return ContainerProps::easySetupDescription(container); + case EasySetupOrderRole: return ContainerProps::easySetupOrder(container); case IsInstalledRole: return m_containers.contains(container); case IsCurrentlyProcessedRole: return container == static_cast(m_currentlyProcessedContainerIndex); case IsDefaultRole: return container == m_defaultContainerIndex; @@ -213,11 +214,6 @@ bool ContainersModel::isAmneziaDnsContainerInstalled(const int serverIndex) return containers.contains(DockerContainer::Dns); } -// bool ContainersModel::isOnlyServicesInstalled(const int serverIndex) -//{ - -//} - QHash ContainersModel::roleNames() const { QHash roles; @@ -231,6 +227,7 @@ QHash ContainersModel::roleNames() const roles[IsEasySetupContainerRole] = "isEasySetupContainer"; roles[EasySetupHeaderRole] = "easySetupHeader"; roles[EasySetupDescriptionRole] = "easySetupDescription"; + roles[EasySetupOrderRole] = "easySetupOrder"; roles[IsInstalledRole] = "isInstalled"; roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed"; diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index 547eea83..741a0620 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -26,6 +26,7 @@ public: IsEasySetupContainerRole, EasySetupHeaderRole, EasySetupDescriptionRole, + EasySetupOrderRole, IsInstalledRole, IsCurrentlyProcessedRole, diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index 6fdc4b3e..b228a7a3 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -27,7 +27,7 @@ PageType { } ] sorters: RoleSorter { - roleName: "dockerContainer" + roleName: "easySetupOrder" sortOrder: Qt.DescendingOrder } } From 893ec2d61c40da40e821f1dd11a32851c19df8ed Mon Sep 17 00:00:00 2001 From: ronoaer Date: Wed, 20 Sep 2023 00:18:10 +0800 Subject: [PATCH 131/278] researching: tried to close drawer easily --- client/ui/qml/Controls2/DrawerType.qml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/client/ui/qml/Controls2/DrawerType.qml b/client/ui/qml/Controls2/DrawerType.qml index 34b141b4..60db1e48 100644 --- a/client/ui/qml/Controls2/DrawerType.qml +++ b/client/ui/qml/Controls2/DrawerType.qml @@ -2,6 +2,7 @@ import QtQuick import QtQuick.Controls Drawer { + id: drawer property bool needCloseButton: true Connections { @@ -66,4 +67,16 @@ Drawer { PageController.updateNavigationBarColor(initialPageNavigationBarColor) } } + + MouseArea { + id: mouseArea + anchors.fill: parent + + onCanceled: { + Drag.cancel() + drawer.close() + } + + preventStealing: false + } } From dd039a612fef510cc8c598ab4f82098f24f86bad Mon Sep 17 00:00:00 2001 From: ronoaer Date: Wed, 20 Sep 2023 14:18:21 +0800 Subject: [PATCH 132/278] used position-changed to closes drawer --- client/ui/qml/Controls2/DrawerType.qml | 28 +++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/client/ui/qml/Controls2/DrawerType.qml b/client/ui/qml/Controls2/DrawerType.qml index 60db1e48..c22d00c2 100644 --- a/client/ui/qml/Controls2/DrawerType.qml +++ b/client/ui/qml/Controls2/DrawerType.qml @@ -4,6 +4,7 @@ import QtQuick.Controls Drawer { id: drawer property bool needCloseButton: true + property bool isOpened: false Connections { target: PageController @@ -61,22 +62,39 @@ Drawer { } } + onOpened: { + isOpened = true + } + onClosed: { + isOpened = false + var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor() if (initialPageNavigationBarColor !== 0xFF1C1D21) { PageController.updateNavigationBarColor(initialPageNavigationBarColor) } } + + onPositionChanged: { + if (isOpened && (position <= 0.99 && position >= 0.95)) { + mouseArea.canceled() + drawer.close() + mouseArea.exited() + dropArea.exited() + } + } + + DropArea { + id: dropArea + } + MouseArea { id: mouseArea anchors.fill: parent - onCanceled: { - Drag.cancel() - drawer.close() + onPressed: { + isOpened = true } - - preventStealing: false } } From de35a26285253aa7d5006d802d01d098efdd69e5 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Thu, 21 Sep 2023 23:03:12 +0800 Subject: [PATCH 133/278] added hover effect for default-server in pagehome --- client/resources.qrc | 1 + client/ui/qml/Controls2/DefaultSeverType.qml | 139 +++++++++++++++++++ client/ui/qml/Controls2/ImageButtonType.qml | 9 +- client/ui/qml/Pages2/PageHome.qml | 69 ++------- 4 files changed, 156 insertions(+), 62 deletions(-) create mode 100644 client/ui/qml/Controls2/DefaultSeverType.qml diff --git a/client/resources.qrc b/client/resources.qrc index 5b4d6ae7..75d7c220 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -215,5 +215,6 @@ ui/qml/Controls2/ListViewWithLabelsType.qml ui/qml/Pages2/PageServiceDnsSettings.qml ui/qml/Controls2/TopCloseButtonType.qml + ui/qml/Controls2/DefaultSeverType.qml diff --git a/client/ui/qml/Controls2/DefaultSeverType.qml b/client/ui/qml/Controls2/DefaultSeverType.qml new file mode 100644 index 00000000..91d2eb52 --- /dev/null +++ b/client/ui/qml/Controls2/DefaultSeverType.qml @@ -0,0 +1,139 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import "TextTypes" + +Item { + id: root + + property string text + property int textMaximumLineCount: 2 + property int textElide: Qt.ElideRight + + property string descriptionText + + property var clickedFunction + + property string rightImageSource + + property string textColor: "#d7d8db" + property string descriptionColor: "#878B91" + property real textOpacity: 1.0 + + property string rightImageColor: "#d7d8db" + + property bool descriptionOnTop: false + + property string defaultServerHostName + property string defaultContainerName + + implicitWidth: content.implicitWidth + content.anchors.topMargin + content.anchors.bottomMargin + implicitHeight: content.implicitHeight + content.anchors.leftMargin + content.anchors.rightMargin + + ColumnLayout { + id: content + + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + + RowLayout { + Layout.topMargin: 24 + Layout.leftMargin: 24 + Layout.rightMargin: 24 + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + + Header1TextType { + Layout.maximumWidth: root.width - 48 - 18 - 12 // todo + + maximumLineCount: 2 + elide: Qt.ElideRight + + text: root.text + Layout.alignment: Qt.AlignLeft + } + + + ImageButtonType { + id: rightImage + + hoverEnabled: false + image: rightImageSource + imageColor: rightImageColor + visible: rightImageSource ? true : false + + implicitSize: 18 + backGroudRadius: 5 + horizontalPadding: 0 + padding: 0 + spacing: 0 + + + Rectangle { + id: rightImageBackground + anchors.fill: parent + radius: 16 + color: "transparent" + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + } + } + + LabelTextType { + Layout.bottomMargin: 44 + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + + text: { + var description = "" + if (ServersModel.isDefaultServerHasWriteAccess()) { + if (SettingsController.isAmneziaDnsEnabled() + && ContainersModel.isAmneziaDnsContainerInstalled(ServersModel.getDefaultServerIndex())) { + description += "Amnezia DNS | " + } + } else { + if (ServersModel.isDefaultServerConfigContainsAmneziaDns()) { + description += "Amnezia DNS | " + } + } + + description += root.defaultContainerName + " | " + root.defaultServerHostName + return description + } + } + } + + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + + onEntered: { + rightImageBackground.color = rightImage.hoveredColor + + root.textOpacity = 0.8 + } + + onExited: { + rightImageBackground.color = rightImage.defaultColor + + root.textOpacity = 1 + } + + onPressedChanged: { + rightImageBackground.color = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor + + root.textOpacity = 0.7 + } + + onClicked: { + if (clickedFunction && typeof clickedFunction === "function") { + clickedFunction() + } + } + } +} diff --git a/client/ui/qml/Controls2/ImageButtonType.qml b/client/ui/qml/Controls2/ImageButtonType.qml index 843599a4..55e19f42 100644 --- a/client/ui/qml/Controls2/ImageButtonType.qml +++ b/client/ui/qml/Controls2/ImageButtonType.qml @@ -15,8 +15,11 @@ Button { property string imageColor: "#878B91" property string disableImageColor: "#2C2D30" - implicitWidth: 40 - implicitHeight: 40 + property int backGroudRadius: 12 + property int implicitSize: 40 + + implicitWidth: implicitSize + implicitHeight: implicitSize hoverEnabled: true @@ -31,7 +34,7 @@ Button { id: background anchors.fill: parent - radius: 12 + radius: backGroudRadius color: { if (root.enabled) { if(root.pressed) { diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index d8796524..125d3e6a 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -50,7 +50,7 @@ PageType { Rectangle { id: buttonBackground - anchors.fill: buttonContent + anchors.fill: defaultServerInfo radius: 16 color: root.defaultColor @@ -67,70 +67,21 @@ PageType { } } - ColumnLayout { - id: buttonContent + DefaultSeverType { + id: defaultServerInfo + height: 130 anchors.right: parent.right anchors.left: parent.left anchors.bottom: parent.bottom - RowLayout { - Layout.topMargin: 24 - Layout.leftMargin: 24 - Layout.rightMargin: 24 - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + text: root.defaultServerName + rightImageSource: "qrc:/images/controls/chevron-down.svg" - spacing: 0 + defaultContainerName: root.defaultContainerName + defaultServerHostName: root.defaultServerHostName - Header1TextType { - Layout.maximumWidth: buttonContent.width - 48 - 18 - 12 // todo - - maximumLineCount: 2 - elide: Qt.ElideRight - - text: root.defaultServerName - horizontalAlignment: Qt.AlignHCenter - } - - Image { - Layout.preferredWidth: 18 - Layout.preferredHeight: 18 - - Layout.leftMargin: 12 - - source: "qrc:/images/controls/chevron-down.svg" - } - } - - LabelTextType { - Layout.bottomMargin: 44 - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - - text: { - var description = "" - if (ServersModel.isDefaultServerHasWriteAccess()) { - if (SettingsController.isAmneziaDnsEnabled() - && ContainersModel.isAmneziaDnsContainerInstalled(ServersModel.getDefaultServerIndex())) { - description += "Amnezia DNS | " - } - } else { - if (ServersModel.isDefaultServerConfigContainsAmneziaDns()) { - description += "Amnezia DNS | " - } - } - - description += root.defaultContainerName + " | " + root.defaultServerHostName - return description - } - } - } - - MouseArea { - anchors.fill: buttonBackground - cursorShape: Qt.PointingHandCursor - hoverEnabled: true - - onClicked: { - menu.visible = true + clickedFunction: function() { + menu.visible = true } } From fd2678ce2f3e3059d93d1b4aa1e996445661f876 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Thu, 21 Sep 2023 14:45:46 -0400 Subject: [PATCH 134/278] Android round icons Icons for some older Android phones that use round icons. --- client/android/AndroidManifest.xml | 3 ++- client/android/res/drawable-hdpi/icon_round.png | Bin 0 -> 4225 bytes client/android/res/drawable-ldpi/icon_round.png | Bin 0 -> 1723 bytes client/android/res/drawable-mdpi/icon_round.png | Bin 0 -> 2624 bytes .../android/res/drawable-xhdpi/icon_round.png | Bin 0 -> 5543 bytes .../android/res/drawable-xxhdpi/icon_round.png | Bin 0 -> 8247 bytes .../android/res/drawable-xxxhdpi/icon_round.png | Bin 0 -> 11186 bytes 7 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 client/android/res/drawable-hdpi/icon_round.png create mode 100644 client/android/res/drawable-ldpi/icon_round.png create mode 100644 client/android/res/drawable-mdpi/icon_round.png create mode 100644 client/android/res/drawable-xhdpi/icon_round.png create mode 100644 client/android/res/drawable-xxhdpi/icon_round.png create mode 100644 client/android/res/drawable-xxxhdpi/icon_round.png diff --git a/client/android/AndroidManifest.xml b/client/android/AndroidManifest.xml index bf431ec6..4ec807e9 100644 --- a/client/android/AndroidManifest.xml +++ b/client/android/AndroidManifest.xml @@ -36,7 +36,8 @@ android:requestLegacyExternalStorage="true" android:allowNativeHeapPointerTagging="false" android:theme="@style/Theme.AppCompat.NoActionBar" - android:icon="@drawable/icon"> + android:icon="@drawable/icon" + android:roundIcon="@drawable/icon_round"> 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 RfExe+002ovPDHLkV1oDrEwKOq literal 0 HcmV?d00001 diff --git a/client/android/res/drawable-mdpi/icon_round.png b/client/android/res/drawable-mdpi/icon_round.png new file mode 100644 index 0000000000000000000000000000000000000000..0ed71d4cc68335cbf2a85260a233b5b2c9806ec1 GIT binary patch literal 2624 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 literal 0 HcmV?d00001 diff --git a/client/android/res/drawable-xxxhdpi/icon_round.png b/client/android/res/drawable-xxxhdpi/icon_round.png new file mode 100644 index 0000000000000000000000000000000000000000..b2b33777fd79fe1bfb8c1473101afdd347649c2e GIT binary patch literal 11186 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 Qg8%>k07*qoM6N<$f@`vY1^@s6 literal 0 HcmV?d00001 From 6afdd8375d83fb10604267a65ed969077655da2f Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 22 Sep 2023 00:37:55 +0500 Subject: [PATCH 135/278] added models, classes and ui files for amnezia wireguard --- client/CMakeLists.txt | 2 + client/amnezia_application.cpp | 7 +- client/amnezia_application.h | 4 +- .../amneziaWireGuardConfigurator.cpp | 15 + .../amneziaWireGuardConfigurator.h | 18 ++ client/configurators/vpn_configurator.cpp | 48 ++-- .../configurators/wireguard_configurator.cpp | 5 +- client/configurators/wireguard_configurator.h | 22 +- client/containers/containers_defs.cpp | 7 + client/containers/containers_defs.h | 1 + client/core/scripts_registry.cpp | 9 +- client/core/servercontroller.cpp | 2 + client/protocols/amneziaWireGuardProtocol.cpp | 10 + client/protocols/amneziaWireGuardProtocol.h | 17 ++ client/protocols/protocols_defs.cpp | 6 + client/protocols/protocols_defs.h | 3 +- client/protocols/vpnprotocol.cpp | 23 +- client/protocols/wireguardprotocol.cpp | 2 +- client/resources.qrc | 1 + client/ui/controllers/pageController.h | 1 + .../protocols/amneziaWireGuardConfigModel.cpp | 70 +++++ .../protocols/amneziaWireGuardConfigModel.h | 39 +++ .../qml/Components/HomeContainersListView.qml | 3 +- .../Components/SettingsContainersListView.qml | 5 + .../PageProtocolAmneziaWireGuardSettings.qml | 272 ++++++++++++++++++ client/ui/qml/Pages2/PageSetupWizardStart.qml | 2 +- 26 files changed, 534 insertions(+), 60 deletions(-) create mode 100644 client/configurators/amneziaWireGuardConfigurator.cpp create mode 100644 client/configurators/amneziaWireGuardConfigurator.h create mode 100644 client/protocols/amneziaWireGuardProtocol.cpp create mode 100644 client/protocols/amneziaWireGuardProtocol.h create mode 100644 client/ui/models/protocols/amneziaWireGuardConfigModel.cpp create mode 100644 client/ui/models/protocols/amneziaWireGuardConfigModel.h create mode 100644 client/ui/qml/Pages2/PageProtocolAmneziaWireGuardSettings.qml diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index ca5161cf..f31a82ce 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -263,6 +263,7 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) ${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.h ${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.h ${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.h + ${CMAKE_CURRENT_LIST_DIR}/protocols/amneziawireguardprotocol.h ) set(SOURCES ${SOURCES} @@ -273,6 +274,7 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) ${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.cpp ${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.cpp ${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.cpp + ${CMAKE_CURRENT_LIST_DIR}/protocols/amneziawireguardprotocol.cpp ) endif() diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 23157468..cef722b1 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -318,8 +318,11 @@ void AmneziaApplication::initModels() m_cloakConfigModel.reset(new CloakConfigModel(this)); m_engine->rootContext()->setContextProperty("CloakConfigModel", m_cloakConfigModel.get()); - m_wireguardConfigModel.reset(new WireGuardConfigModel(this)); - m_engine->rootContext()->setContextProperty("WireGuardConfigModel", m_wireguardConfigModel.get()); + m_wireGuardConfigModel.reset(new WireGuardConfigModel(this)); + m_engine->rootContext()->setContextProperty("WireGuardConfigModel", m_wireGuardConfigModel.get()); + + m_amneziaWireGuardConfigModel.reset(new AmneziaWireGuardConfigModel(this)); + m_engine->rootContext()->setContextProperty("AmneziaWireGuardConfigModel", m_amneziaWireGuardConfigModel.get()); #ifdef Q_OS_WINDOWS m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this)); diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 2dd74fcb..77e50c92 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -31,6 +31,7 @@ #ifdef Q_OS_WINDOWS #include "ui/models/protocols/ikev2ConfigModel.h" #endif +#include "ui/models/protocols/amneziaWireGuardConfigModel.h" #include "ui/models/protocols/openvpnConfigModel.h" #include "ui/models/protocols/shadowsocksConfigModel.h" #include "ui/models/protocols/wireguardConfigModel.h" @@ -97,7 +98,8 @@ private: QScopedPointer m_openVpnConfigModel; QScopedPointer m_shadowSocksConfigModel; QScopedPointer m_cloakConfigModel; - QScopedPointer m_wireguardConfigModel; + QScopedPointer m_wireGuardConfigModel; + QScopedPointer m_amneziaWireGuardConfigModel; #ifdef Q_OS_WINDOWS QScopedPointer m_ikev2ConfigModel; #endif diff --git a/client/configurators/amneziaWireGuardConfigurator.cpp b/client/configurators/amneziaWireGuardConfigurator.cpp new file mode 100644 index 00000000..56f0c68e --- /dev/null +++ b/client/configurators/amneziaWireGuardConfigurator.cpp @@ -0,0 +1,15 @@ +#include "amneziaWireGuardConfigurator.h" + +AmneziaWireGuardConfigurator::AmneziaWireGuardConfigurator(std::shared_ptr settings, QObject *parent) + : WireguardConfigurator(settings, parent) +{ +} + +QString AmneziaWireGuardConfigurator::genAmneziaWireGuardConfig(const ServerCredentials &credentials, + DockerContainer container, + const QJsonObject &containerConfig, ErrorCode *errorCode) +{ + auto config = WireguardConfigurator::genWireguardConfig(credentials, container, containerConfig, errorCode); + + return config; +} diff --git a/client/configurators/amneziaWireGuardConfigurator.h b/client/configurators/amneziaWireGuardConfigurator.h new file mode 100644 index 00000000..02961cf1 --- /dev/null +++ b/client/configurators/amneziaWireGuardConfigurator.h @@ -0,0 +1,18 @@ +#ifndef AMNEZIAWIREGUARDCONFIGURATOR_H +#define AMNEZIAWIREGUARDCONFIGURATOR_H + +#include + +#include "wireguard_configurator.h" + +class AmneziaWireGuardConfigurator : public WireguardConfigurator +{ + Q_OBJECT +public: + AmneziaWireGuardConfigurator(std::shared_ptr settings, QObject *parent = nullptr); + + QString genAmneziaWireGuardConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr); +}; + +#endif // AMNEZIAWIREGUARDCONFIGURATOR_H diff --git a/client/configurators/vpn_configurator.cpp b/client/configurators/vpn_configurator.cpp index ceb6a5a4..7f0e95df 100644 --- a/client/configurators/vpn_configurator.cpp +++ b/client/configurators/vpn_configurator.cpp @@ -1,21 +1,21 @@ #include "vpn_configurator.h" -#include "openvpn_configurator.h" #include "cloak_configurator.h" -#include "shadowsocks_configurator.h" -#include "wireguard_configurator.h" #include "ikev2_configurator.h" +#include "openvpn_configurator.h" +#include "shadowsocks_configurator.h" #include "ssh_configurator.h" +#include "wireguard_configurator.h" #include -#include #include +#include #include "containers/containers_defs.h" -#include "utilities.h" #include "settings.h" +#include "utilities.h" -VpnConfigurator::VpnConfigurator(std::shared_ptr settings, QObject *parent): - ConfiguratorBase(settings, parent) +VpnConfigurator::VpnConfigurator(std::shared_ptr settings, QObject *parent) + : ConfiguratorBase(settings, parent) { openVpnConfigurator = std::shared_ptr(new OpenVpnConfigurator(settings, this)); shadowSocksConfigurator = std::shared_ptr(new ShadowSocksConfigurator(settings, this)); @@ -25,8 +25,8 @@ VpnConfigurator::VpnConfigurator(std::shared_ptr settings, QObject *pa sshConfigurator = std::shared_ptr(new SshConfigurator(settings, this)); } -QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentials, - DockerContainer container, const QJsonObject &containerConfig, Proto proto, ErrorCode *errorCode) +QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, Proto proto, ErrorCode *errorCode) { switch (proto) { case Proto::OpenVpn: @@ -35,17 +35,17 @@ QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentia case Proto::ShadowSocks: return shadowSocksConfigurator->genShadowSocksConfig(credentials, container, containerConfig, errorCode); - case Proto::Cloak: - return cloakConfigurator->genCloakConfig(credentials, container, containerConfig, errorCode); + case Proto::Cloak: return cloakConfigurator->genCloakConfig(credentials, container, containerConfig, errorCode); case Proto::WireGuard: return wireguardConfigurator->genWireguardConfig(credentials, container, containerConfig, errorCode); - case Proto::Ikev2: - return ikev2Configurator->genIkev2Config(credentials, container, containerConfig, errorCode); + case Proto::AmneziaWireGuard: + return wireguardConfigurator->genWireguardConfig(credentials, container, containerConfig, errorCode); - default: - return ""; + case Proto::Ikev2: return ikev2Configurator->genIkev2Config(credentials, container, containerConfig, errorCode); + + default: return ""; } } @@ -62,8 +62,8 @@ QPair VpnConfigurator::getDnsForConfig(int serverIndex) if (dns.first.isEmpty() || !Utils::checkIPv4Format(dns.first)) { if (useAmneziaDns && m_settings->containers(serverIndex).contains(DockerContainer::Dns)) { dns.first = protocols::dns::amneziaDnsIp; - } - else dns.first = m_settings->primaryDns(); + } else + dns.first = m_settings->primaryDns(); } if (dns.second.isEmpty() || !Utils::checkIPv4Format(dns.second)) { dns.second = m_settings->secondaryDns(); @@ -73,8 +73,8 @@ QPair VpnConfigurator::getDnsForConfig(int serverIndex) return dns; } -QString &VpnConfigurator::processConfigWithDnsSettings(int serverIndex, DockerContainer container, - Proto proto, QString &config) +QString &VpnConfigurator::processConfigWithDnsSettings(int serverIndex, DockerContainer container, Proto proto, + QString &config) { auto dns = getDnsForConfig(serverIndex); @@ -84,8 +84,8 @@ QString &VpnConfigurator::processConfigWithDnsSettings(int serverIndex, DockerCo return config; } -QString &VpnConfigurator::processConfigWithLocalSettings(int serverIndex, DockerContainer container, - Proto proto, QString &config) +QString &VpnConfigurator::processConfigWithLocalSettings(int serverIndex, DockerContainer container, Proto proto, + QString &config) { processConfigWithDnsSettings(serverIndex, container, proto, config); @@ -95,8 +95,8 @@ QString &VpnConfigurator::processConfigWithLocalSettings(int serverIndex, Docker return config; } -QString &VpnConfigurator::processConfigWithExportSettings(int serverIndex, DockerContainer container, - Proto proto, QString &config) +QString &VpnConfigurator::processConfigWithExportSettings(int serverIndex, DockerContainer container, Proto proto, + QString &config) { processConfigWithDnsSettings(serverIndex, container, proto, config); @@ -107,7 +107,7 @@ QString &VpnConfigurator::processConfigWithExportSettings(int serverIndex, Docke } void VpnConfigurator::updateContainerConfigAfterInstallation(DockerContainer container, QJsonObject &containerConfig, - const QString &stdOut) + const QString &stdOut) { Proto mainProto = ContainerProps::defaultProtocol(container); diff --git a/client/configurators/wireguard_configurator.cpp b/client/configurators/wireguard_configurator.cpp index 14059977..02716b72 100644 --- a/client/configurators/wireguard_configurator.cpp +++ b/client/configurators/wireguard_configurator.cpp @@ -62,7 +62,10 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon { WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys(); connData.host = credentials.hostName; - connData.port = containerConfig.value(config_key::port).toString(protocols::wireguard::defaultPort); + connData.port = containerConfig.value(config_key::wireguard) + .toObject() + .value(config_key::port) + .toString(protocols::wireguard::defaultPort); if (connData.clientPrivKey.isEmpty() || connData.clientPubKey.isEmpty()) { if (errorCode) diff --git a/client/configurators/wireguard_configurator.h b/client/configurators/wireguard_configurator.h index 7674eb06..140acc47 100644 --- a/client/configurators/wireguard_configurator.h +++ b/client/configurators/wireguard_configurator.h @@ -7,32 +7,32 @@ #include "configurator_base.h" #include "core/defs.h" -class WireguardConfigurator : ConfiguratorBase +class WireguardConfigurator : public ConfiguratorBase { Q_OBJECT public: WireguardConfigurator(std::shared_ptr settings, QObject *parent = nullptr); - struct ConnectionData { + struct ConnectionData + { QString clientPrivKey; // client private key - QString clientPubKey; // client public key - QString clientIP; // internal client IP address - QString serverPubKey; // tls-auth key - QString pskKey; // preshared key - QString host; // host ip + QString clientPubKey; // client public key + QString clientIP; // internal client IP address + QString serverPubKey; // tls-auth key + QString pskKey; // preshared key + QString host; // host ip QString port; }; QString genWireguardConfig(const ServerCredentials &credentials, DockerContainer container, - const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr); + const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr); QString processConfigWithLocalSettings(QString config); QString processConfigWithExportSettings(QString config); - private: - ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, - DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr); + ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container, + const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr); ConnectionData genClientKeys(); }; diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index 20fc59f4..21f7b044 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -84,6 +84,7 @@ QMap ContainerProps::containerHumanNames() { DockerContainer::ShadowSocks, "ShadowSocks" }, { DockerContainer::Cloak, "OpenVPN over Cloak" }, { DockerContainer::WireGuard, "WireGuard" }, + { DockerContainer::AmneziaWireGuard, "Amnezia WireGuard" }, { DockerContainer::Ipsec, QObject::tr("IPsec") }, { DockerContainer::TorWebSite, QObject::tr("Website in Tor network") }, @@ -107,6 +108,9 @@ QMap ContainerProps::containerDescriptions() { DockerContainer::WireGuard, QObject::tr("WireGuard - New popular VPN protocol with high performance, high speed and low power " "consumption. Recommended for regions with low levels of censorship.") }, + { DockerContainer::AmneziaWireGuard, + QObject::tr("WireGuard - New popular VPN protocol with high performance, high speed and low power " + "consumption. Recommended for regions with low levels of censorship.") }, { DockerContainer::Ipsec, QObject::tr("IKEv2 - Modern stable protocol, a bit faster than others, restores connection after " "signal loss. It has native support on the latest versions of Android and iOS.") }, @@ -127,6 +131,7 @@ QMap ContainerProps::containerDetailedDescriptions() QObject::tr("Container with OpenVpn and ShadowSocks protocols " "configured with traffic masking by Cloak plugin") }, { DockerContainer::WireGuard, QObject::tr("WireGuard container") }, + { DockerContainer::WireGuard, QObject::tr("Amnezia WireGuard container") }, { DockerContainer::Ipsec, QObject::tr("IPsec container") }, { DockerContainer::TorWebSite, QObject::tr("Website in Tor network") }, @@ -143,6 +148,7 @@ amnezia::ServiceType ContainerProps::containerService(DockerContainer c) case DockerContainer::Cloak: return ServiceType::Vpn; case DockerContainer::ShadowSocks: return ServiceType::Vpn; case DockerContainer::WireGuard: return ServiceType::Vpn; + case DockerContainer::AmneziaWireGuard: return ServiceType::Vpn; case DockerContainer::Ipsec: return ServiceType::Vpn; case DockerContainer::TorWebSite: return ServiceType::Other; case DockerContainer::Dns: return ServiceType::Other; @@ -160,6 +166,7 @@ Proto ContainerProps::defaultProtocol(DockerContainer c) case DockerContainer::Cloak: return Proto::Cloak; case DockerContainer::ShadowSocks: return Proto::ShadowSocks; case DockerContainer::WireGuard: return Proto::WireGuard; + case DockerContainer::AmneziaWireGuard: return Proto::AmneziaWireGuard; case DockerContainer::Ipsec: return Proto::Ikev2; case DockerContainer::TorWebSite: return Proto::TorWebSite; diff --git a/client/containers/containers_defs.h b/client/containers/containers_defs.h index 9ca51a96..774611c8 100644 --- a/client/containers/containers_defs.h +++ b/client/containers/containers_defs.h @@ -20,6 +20,7 @@ namespace amnezia ShadowSocks, Cloak, WireGuard, + AmneziaWireGuard, Ipsec, // non-vpn diff --git a/client/core/scripts_registry.cpp b/client/core/scripts_registry.cpp index 1b379ea1..31508152 100644 --- a/client/core/scripts_registry.cpp +++ b/client/core/scripts_registry.cpp @@ -1,8 +1,8 @@ #include "scripts_registry.h" -#include #include #include +#include QString amnezia::scriptFolder(amnezia::DockerContainer container) { @@ -11,11 +11,12 @@ QString amnezia::scriptFolder(amnezia::DockerContainer container) case DockerContainer::Cloak: return QLatin1String("openvpn_cloak"); case DockerContainer::ShadowSocks: return QLatin1String("openvpn_shadowsocks"); case DockerContainer::WireGuard: return QLatin1String("wireguard"); + case DockerContainer::AmneziaWireGuard: return QLatin1String("wireguard"); case DockerContainer::Ipsec: return QLatin1String("ipsec"); case DockerContainer::TorWebSite: return QLatin1String("website_tor"); case DockerContainer::Dns: return QLatin1String("dns"); - //case DockerContainer::FileShare: return QLatin1String("file_share"); + // case DockerContainer::FileShare: return QLatin1String("file_share"); case DockerContainer::Sftp: return QLatin1String("sftp"); default: return ""; } @@ -52,7 +53,7 @@ QString amnezia::scriptData(amnezia::SharedScriptType type) { QString fileName = QString(":/server_scripts/%1").arg(amnezia::scriptName(type)); QFile file(fileName); - if (! file.open(QIODevice::ReadOnly)) { + if (!file.open(QIODevice::ReadOnly)) { qDebug() << "Warning: script missing" << fileName; return ""; } @@ -67,7 +68,7 @@ QString amnezia::scriptData(amnezia::ProtocolScriptType type, DockerContainer co { QString fileName = QString(":/server_scripts/%1/%2").arg(amnezia::scriptFolder(container), amnezia::scriptName(type)); QFile file(fileName); - if (! file.open(QIODevice::ReadOnly)) { + if (!file.open(QIODevice::ReadOnly)) { qDebug() << "Warning: script missing" << fileName; return ""; } diff --git a/client/core/servercontroller.cpp b/client/core/servercontroller.cpp index b0f8146f..27213dc3 100644 --- a/client/core/servercontroller.cpp +++ b/client/core/servercontroller.cpp @@ -486,6 +486,8 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential const QJsonObject &cloakConfig = config.value(ProtocolProps::protoToString(Proto::Cloak)).toObject(); const QJsonObject &ssConfig = config.value(ProtocolProps::protoToString(Proto::ShadowSocks)).toObject(); const QJsonObject &wireguarConfig = config.value(ProtocolProps::protoToString(Proto::WireGuard)).toObject(); + const QJsonObject &amneziaWireguarConfig = + config.value(ProtocolProps::protoToString(Proto::AmneziaWireGuard)).toObject(); const QJsonObject &sftpConfig = config.value(ProtocolProps::protoToString(Proto::Sftp)).toObject(); Vars vars; diff --git a/client/protocols/amneziaWireGuardProtocol.cpp b/client/protocols/amneziaWireGuardProtocol.cpp new file mode 100644 index 00000000..b4c5b430 --- /dev/null +++ b/client/protocols/amneziaWireGuardProtocol.cpp @@ -0,0 +1,10 @@ +#include "amneziaWireGuardProtocol.h" + +AmneziaWireGuardProtocol::AmneziaWireGuardProtocol(const QJsonObject &configuration, QObject *parent) + : WireguardProtocol(configuration, parent) +{ +} + +AmneziaWireGuardProtocol::~AmneziaWireGuardProtocol() +{ +} diff --git a/client/protocols/amneziaWireGuardProtocol.h b/client/protocols/amneziaWireGuardProtocol.h new file mode 100644 index 00000000..329a585e --- /dev/null +++ b/client/protocols/amneziaWireGuardProtocol.h @@ -0,0 +1,17 @@ +#ifndef AMNEZIAWIREGUARDPROTOCOL_H +#define AMNEZIAWIREGUARDPROTOCOL_H + +#include + +#include "wireguardprotocol.h" + +class AmneziaWireGuardProtocol : public WireguardProtocol +{ + Q_OBJECT + +public: + explicit AmneziaWireGuardProtocol(const QJsonObject &configuration, QObject *parent = nullptr); + virtual ~AmneziaWireGuardProtocol() override; +}; + +#endif // AMNEZIAWIREGUARDPROTOCOL_H diff --git a/client/protocols/protocols_defs.cpp b/client/protocols/protocols_defs.cpp index 5f8600db..64cdd003 100644 --- a/client/protocols/protocols_defs.cpp +++ b/client/protocols/protocols_defs.cpp @@ -66,6 +66,7 @@ QMap ProtocolProps::protocolHumanNames() { Proto::ShadowSocks, "ShadowSocks" }, { Proto::Cloak, "Cloak" }, { Proto::WireGuard, "WireGuard" }, + { Proto::WireGuard, "Amnezia WireGuard" }, { Proto::Ikev2, "IKEv2" }, { Proto::L2tp, "L2TP" }, @@ -88,6 +89,7 @@ amnezia::ServiceType ProtocolProps::protocolService(Proto p) case Proto::Cloak: return ServiceType::Vpn; case Proto::ShadowSocks: return ServiceType::Vpn; case Proto::WireGuard: return ServiceType::Vpn; + case Proto::AmneziaWireGuard: return ServiceType::Vpn; case Proto::TorWebSite: return ServiceType::Other; case Proto::Dns: return ServiceType::Other; case Proto::FileShare: return ServiceType::Other; @@ -103,6 +105,7 @@ int ProtocolProps::defaultPort(Proto p) case Proto::Cloak: return 443; case Proto::ShadowSocks: return 6789; case Proto::WireGuard: return 51820; + case Proto::AmneziaWireGuard: return 55424; case Proto::Ikev2: return -1; case Proto::L2tp: return -1; @@ -122,6 +125,7 @@ bool ProtocolProps::defaultPortChangeable(Proto p) case Proto::Cloak: return true; case Proto::ShadowSocks: return true; case Proto::WireGuard: return true; + case Proto::AmneziaWireGuard: return true; case Proto::Ikev2: return false; case Proto::L2tp: return false; @@ -140,6 +144,7 @@ TransportProto ProtocolProps::defaultTransportProto(Proto p) case Proto::Cloak: return TransportProto::Tcp; case Proto::ShadowSocks: return TransportProto::Tcp; case Proto::WireGuard: return TransportProto::Udp; + case Proto::AmneziaWireGuard: return TransportProto::Udp; case Proto::Ikev2: return TransportProto::Udp; case Proto::L2tp: return TransportProto::Udp; // non-vpn @@ -158,6 +163,7 @@ bool ProtocolProps::defaultTransportProtoChangeable(Proto p) case Proto::Cloak: return false; case Proto::ShadowSocks: return false; case Proto::WireGuard: return false; + case Proto::AmneziaWireGuard: return false; case Proto::Ikev2: return false; case Proto::L2tp: return false; // non-vpn diff --git a/client/protocols/protocols_defs.h b/client/protocols/protocols_defs.h index 9472164b..4e72e318 100644 --- a/client/protocols/protocols_defs.h +++ b/client/protocols/protocols_defs.h @@ -2,8 +2,8 @@ #define PROTOCOLS_DEFS_H #include -#include #include +#include namespace amnezia { @@ -158,6 +158,7 @@ namespace amnezia ShadowSocks, Cloak, WireGuard, + AmneziaWireGuard, Ikev2, L2tp, diff --git a/client/protocols/vpnprotocol.cpp b/client/protocols/vpnprotocol.cpp index 841d307c..527ede47 100644 --- a/client/protocols/vpnprotocol.cpp +++ b/client/protocols/vpnprotocol.cpp @@ -1,22 +1,21 @@ #include #include -#include "vpnprotocol.h" #include "core/errorstrings.h" +#include "vpnprotocol.h" #if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) -#include "openvpnprotocol.h" -#include "shadowsocksvpnprotocol.h" -#include "openvpnovercloakprotocol.h" -#include "wireguardprotocol.h" + #include "openvpnovercloakprotocol.h" + #include "openvpnprotocol.h" + #include "shadowsocksvpnprotocol.h" + #include "wireguardprotocol.h" #endif #ifdef Q_OS_WINDOWS -#include "ikev2_vpn_protocol_windows.h" + #include "ikev2_vpn_protocol_windows.h" #endif - -VpnProtocol::VpnProtocol(const QJsonObject &configuration, QObject* parent) +VpnProtocol::VpnProtocol(const QJsonObject &configuration, QObject *parent) : QObject(parent), m_connectionState(Vpn::ConnectionState::Unknown), m_rawConfig(configuration), @@ -31,7 +30,7 @@ VpnProtocol::VpnProtocol(const QJsonObject &configuration, QObject* parent) void VpnProtocol::setLastError(ErrorCode lastError) { m_lastError = lastError; - if (lastError){ + if (lastError) { setConnectionState(Vpn::ConnectionState::Error); } qCritical().noquote() << "VpnProtocol error, code" << m_lastError << errorString(m_lastError); @@ -103,7 +102,7 @@ QString VpnProtocol::vpnGateway() const return m_vpnGateway; } -VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject& configuration) +VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &configuration) { switch (container) { #if defined(Q_OS_WINDOWS) @@ -114,6 +113,7 @@ VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject& case DockerContainer::Cloak: return new OpenVpnOverCloakProtocol(configuration); case DockerContainer::ShadowSocks: return new ShadowSocksVpnProtocol(configuration); case DockerContainer::WireGuard: return new WireguardProtocol(configuration); + case DockerContainer::AmneziaWireGuard: return new WireguardProtocol(configuration); #endif default: return nullptr; } @@ -135,8 +135,7 @@ QString VpnProtocol::textConnectionState(Vpn::ConnectionState connectionState) case Vpn::ConnectionState::Disconnecting: return tr("Disconnecting..."); case Vpn::ConnectionState::Reconnecting: return tr("Reconnecting..."); case Vpn::ConnectionState::Error: return tr("Error"); - default: - ; + default:; } return QString(); diff --git a/client/protocols/wireguardprotocol.cpp b/client/protocols/wireguardprotocol.cpp index 7466d1af..eb37f67a 100644 --- a/client/protocols/wireguardprotocol.cpp +++ b/client/protocols/wireguardprotocol.cpp @@ -18,7 +18,7 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject * // MZ #if defined(MZ_LINUX) - //m_impl.reset(new LinuxController()); + // m_impl.reset(new LinuxController()); #elif defined(Q_OS_MAC) || defined(Q_OS_WIN) m_impl.reset(new LocalSocketController()); connect(m_impl.get(), &ControllerImpl::connected, this, diff --git a/client/resources.qrc b/client/resources.qrc index 5b4d6ae7..44c61172 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -215,5 +215,6 @@ ui/qml/Controls2/ListViewWithLabelsType.qml ui/qml/Pages2/PageServiceDnsSettings.qml ui/qml/Controls2/TopCloseButtonType.qml + ui/qml/Pages2/PageProtocolAmneziaWireGuardSettings.qml diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index a8f883fe..cf248900 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -49,6 +49,7 @@ namespace PageLoader PageProtocolShadowSocksSettings, PageProtocolCloakSettings, PageProtocolWireGuardSettings, + PageProtocolAmneziaWireGuardSettings, PageProtocolIKev2Settings, PageProtocolRaw }; diff --git a/client/ui/models/protocols/amneziaWireGuardConfigModel.cpp b/client/ui/models/protocols/amneziaWireGuardConfigModel.cpp new file mode 100644 index 00000000..9cf4ed14 --- /dev/null +++ b/client/ui/models/protocols/amneziaWireGuardConfigModel.cpp @@ -0,0 +1,70 @@ +#include "amneziaWireGuardConfigModel.h" + +#include "protocols/protocols_defs.h" + +AmneziaWireGuardConfigModel::AmneziaWireGuardConfigModel(QObject *parent) : QAbstractListModel(parent) +{ +} + +int AmneziaWireGuardConfigModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 1; +} + +bool AmneziaWireGuardConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { + return false; + } + + switch (role) { + case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break; + case Roles::CipherRole: m_protocolConfig.insert(config_key::cipher, value.toString()); break; + } + + emit dataChanged(index, index, QList { role }); + return true; +} + +QVariant AmneziaWireGuardConfigModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { + return false; + } + + switch (role) { + case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort); + case Roles::CipherRole: + return m_protocolConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher); + } + + return QVariant(); +} + +void AmneziaWireGuardConfigModel::updateModel(const QJsonObject &config) +{ + beginResetModel(); + m_container = ContainerProps::containerFromString(config.value(config_key::container).toString()); + + m_fullConfig = config; + QJsonObject protocolConfig = config.value(config_key::wireguard).toObject(); + + endResetModel(); +} + +QJsonObject AmneziaWireGuardConfigModel::getConfig() +{ + m_fullConfig.insert(config_key::wireguard, m_protocolConfig); + return m_fullConfig; +} + +QHash AmneziaWireGuardConfigModel::roleNames() const +{ + QHash roles; + + roles[PortRole] = "port"; + roles[CipherRole] = "cipher"; + + return roles; +} diff --git a/client/ui/models/protocols/amneziaWireGuardConfigModel.h b/client/ui/models/protocols/amneziaWireGuardConfigModel.h new file mode 100644 index 00000000..b798c289 --- /dev/null +++ b/client/ui/models/protocols/amneziaWireGuardConfigModel.h @@ -0,0 +1,39 @@ +#ifndef AMNEZIAWIREGUARDCONFIGMODEL_H +#define AMNEZIAWIREGUARDCONFIGMODEL_H + +#include +#include + +#include "containers/containers_defs.h" + +class AmneziaWireGuardConfigModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + PortRole = Qt::UserRole + 1, + CipherRole + }; + + explicit AmneziaWireGuardConfigModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +public slots: + void updateModel(const QJsonObject &config); + QJsonObject getConfig(); + +protected: + QHash roleNames() const override; + +private: + DockerContainer m_container; + QJsonObject m_protocolConfig; + QJsonObject m_fullConfig; +}; + +#endif // AMNEZIAWIREGUARDCONFIGMODEL_H diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index 4708128f..037a666d 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -72,8 +72,7 @@ ListView { containersDropDown.menuVisible = false - if (needReconnected && - (ConnectionController.isConnected || ConnectionController.isConnectionInProgress)) { + if (needReconnected && (ConnectionController.isConnected || ConnectionController.isConnectionInProgress)) { PageController.showNotificationMessage(qsTr("Reconnect via VPN Procotol: ") + name) PageController.goToPageHome() menu.visible = false diff --git a/client/ui/qml/Components/SettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml index edd96bd7..250ba1eb 100644 --- a/client/ui/qml/Components/SettingsContainersListView.qml +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -64,6 +64,11 @@ ListView { // goToPage(PageEnum.PageProtocolWireGuardSettings) break } + case ContainerEnum.AmneziaWireGuard: { + WireGuardConfigModel.updateModel(config) + PageController.goToPage(PageEnum.PageProtocolAmneziaWireGuardSettings) + break + } case ContainerEnum.Ipsec: { ProtocolsModel.updateModel(config) PageController.goToPage(PageEnum.PageProtocolRaw) diff --git a/client/ui/qml/Pages2/PageProtocolAmneziaWireGuardSettings.qml b/client/ui/qml/Pages2/PageProtocolAmneziaWireGuardSettings.qml new file mode 100644 index 00000000..a905f47a --- /dev/null +++ b/client/ui/qml/Pages2/PageProtocolAmneziaWireGuardSettings.qml @@ -0,0 +1,272 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import SortFilterProxyModel 0.2 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + ColumnLayout { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + } + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.implicitHeight + + Column { + id: content + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + enabled: ServersModel.isCurrentlyProcessedServerHasWriteAccess() + + ListView { + id: listview + + width: parent.width + height: listview.contentItem.height + + clip: true + interactive: false + + model: AmneziaWireGuardConfigModel + + delegate: Item { + implicitWidth: listview.width + implicitHeight: col.implicitHeight + + ColumnLayout { + id: col + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + spacing: 0 + + HeaderType { + Layout.fillWidth: true + + headerText: qsTr("Amnezia WireGuard settings") + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + Layout.topMargin: 40 + + headerText: qsTr("Port") + textFieldText: port + textField.maximumLength: 5 + textField.validator: IntValidator { bottom: 1; top: 65535 } + + textField.onEditingFinished: { + if (textFieldText !== port) { + port = textFieldText + } + } + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Junk packet count") + textFieldText: port + + textField.onEditingFinished: { + if (textFieldText !== port) { + port = textFieldText + } + } + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Junk packet minimum size") + textFieldText: port + + textField.onEditingFinished: { + if (textFieldText !== port) { + port = textFieldText + } + } + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Junk packet maximum size") + textFieldText: port + + textField.onEditingFinished: { + if (textFieldText !== port) { + port = textFieldText + } + } + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Init packet junk size") + textFieldText: port + + textField.onEditingFinished: { + if (textFieldText !== port) { + port = textFieldText + } + } + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Response packet junk size") + textFieldText: port + + textField.onEditingFinished: { + if (textFieldText !== port) { + port = textFieldText + } + } + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Init packet magic header") + textFieldText: port + + textField.onEditingFinished: { + if (textFieldText !== port) { + port = textFieldText + } + } + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Response packet magic header") + textFieldText: port + + textField.onEditingFinished: { + if (textFieldText !== port) { + port = textFieldText + } + } + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Transport packet magic header") + textFieldText: port + + textField.onEditingFinished: { + if (textFieldText !== port) { + port = textFieldText + } + } + } + + TextFieldWithHeaderType { + Layout.fillWidth: true + Layout.topMargin: 16 + + headerText: qsTr("Underload packet magic header") + textFieldText: port + + textField.onEditingFinished: { + if (textFieldText !== port) { + port = textFieldText + } + } + } + + BasicButtonType { + Layout.topMargin: 24 + Layout.leftMargin: -8 + implicitHeight: 32 + + defaultColor: "transparent" + hoveredColor: Qt.rgba(1, 1, 1, 0.08) + pressedColor: Qt.rgba(1, 1, 1, 0.12) + textColor: "#EB5757" + + text: qsTr("Remove Amnezia WireGuard") + + onClicked: { + questionDrawer.headerText = qsTr("Remove Amnezia WireGuard from server?") + questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it") + questionDrawer.yesButtonText = qsTr("Continue") + questionDrawer.noButtonText = qsTr("Cancel") + + questionDrawer.yesButtonFunction = function() { + questionDrawer.visible = false + PageController.goToPage(PageEnum.PageDeinstalling) + InstallController.removeCurrentlyProcessedContainer() + } + questionDrawer.noButtonFunction = function() { + questionDrawer.visible = false + } + questionDrawer.visible = true + } + } + + BasicButtonType { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.bottomMargin: 24 + + text: qsTr("Save and Restart Amnezia") + + onClicked: { + forceActiveFocus() +// PageController.showBusyIndicator(true) +// InstallController.updateContainer(ShadowSocksConfigModel.getConfig()) +// PageController.showBusyIndicator(false) + } + } + } + } + } + } + + QuestionDrawer { + id: questionDrawer + } + } +} diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index 9f5e57a5..ba78c985 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -62,7 +62,7 @@ PageType { function onInstallationErrorOccurred(errorMessage) { PageController.showErrorMessage(errorMessage) - var currentPageName = tabBarStackView.currentItem.objectName + var currentPageName = stackView.currentItem.objectName if (currentPageName === PageController.getPagePath(PageEnum.PageSetupWizardInstalling)) { PageController.closePage() From 49923c421439ae181debffe2b23283a7cd10bd2d Mon Sep 17 00:00:00 2001 From: ronoaer Date: Sun, 24 Sep 2023 08:21:27 +0800 Subject: [PATCH 136/278] renamed deaultservertype, and moved to components --- client/resources.qrc | 2 +- .../DefaultSeverType.qml => Components/HomeRootMenuButton.qml} | 3 ++- client/ui/qml/Pages2/PageHome.qml | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) rename client/ui/qml/{Controls2/DefaultSeverType.qml => Components/HomeRootMenuButton.qml} (98%) diff --git a/client/resources.qrc b/client/resources.qrc index 75d7c220..ff101a5d 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -215,6 +215,6 @@ ui/qml/Controls2/ListViewWithLabelsType.qml ui/qml/Pages2/PageServiceDnsSettings.qml ui/qml/Controls2/TopCloseButtonType.qml - ui/qml/Controls2/DefaultSeverType.qml + ui/qml/Components/HomeRootMenuButton.qml diff --git a/client/ui/qml/Controls2/DefaultSeverType.qml b/client/ui/qml/Components/HomeRootMenuButton.qml similarity index 98% rename from client/ui/qml/Controls2/DefaultSeverType.qml rename to client/ui/qml/Components/HomeRootMenuButton.qml index 91d2eb52..aa6d8f9b 100644 --- a/client/ui/qml/Controls2/DefaultSeverType.qml +++ b/client/ui/qml/Components/HomeRootMenuButton.qml @@ -2,7 +2,8 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts -import "TextTypes" +import "../Controls2/TextTypes" +import "../Controls2" Item { id: root diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 125d3e6a..77c60684 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -67,7 +67,7 @@ PageType { } } - DefaultSeverType { + HomeRootMenuButton { id: defaultServerInfo height: 130 anchors.right: parent.right From 52400252ddf3c02b43e4d46ec6f1e1c776b9a640 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Sun, 24 Sep 2023 07:06:52 -0400 Subject: [PATCH 137/278] Fix disconnect button for desktop WG (#345) * Fix disconnect button for desktop WG --- client/daemon/daemonlocalserverconnection.cpp | 2 +- client/mozilla/localsocketcontroller.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/client/daemon/daemonlocalserverconnection.cpp b/client/daemon/daemonlocalserverconnection.cpp index 43c67f16..1a49b7e5 100644 --- a/client/daemon/daemonlocalserverconnection.cpp +++ b/client/daemon/daemonlocalserverconnection.cpp @@ -108,7 +108,7 @@ void DaemonLocalServerConnection::parseCommand(const QByteArray& data) { } if (type == "deactivate") { - Daemon::instance()->deactivate(); + Daemon::instance()->deactivate(true); return; } diff --git a/client/mozilla/localsocketcontroller.cpp b/client/mozilla/localsocketcontroller.cpp index 40bc0bba..00811500 100644 --- a/client/mozilla/localsocketcontroller.cpp +++ b/client/mozilla/localsocketcontroller.cpp @@ -175,6 +175,7 @@ void LocalSocketController::deactivate() { QJsonObject json; json.insert("type", "deactivate"); write(json); + emit disconnected(); } void LocalSocketController::checkStatus() { From b4df5c076eda5df5832e89f29208d72bcb3456b6 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Sun, 24 Sep 2023 11:57:59 -0400 Subject: [PATCH 138/278] Fix Linux App startup icon (#344) * Fix Linux App startup icon * Use project version from cmake * Set Release date automatically --- CMakeLists.txt | 5 ++++- deploy/data/linux/AmneziaVPN.png | Bin 0 -> 28646 bytes .../share/applications/AmneziaVPN.desktop | 10 ---------- .../client/share/icons/AmneziaVPN_Logo.png | Bin 56043 -> 0 bytes deploy/data/linux/post_install.sh | 1 + deploy/data/linux/post_uninstall.sh | 5 +++++ deploy/installer/config.cmake | 5 +++++ .../config/AmneziaVPN.desktop.in} | 6 +++--- deploy/installer/config/linux.xml.in | 2 +- 9 files changed, 19 insertions(+), 15 deletions(-) create mode 100644 deploy/data/linux/AmneziaVPN.png delete mode 100755 deploy/data/linux/client/share/applications/AmneziaVPN.desktop delete mode 100755 deploy/data/linux/client/share/icons/AmneziaVPN_Logo.png rename deploy/{data/linux/AmneziaVPN.desktop => installer/config/AmneziaVPN.desktop.in} (64%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 50bd621c..716d6a7f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,10 @@ project(${PROJECT} VERSION 4.0.7.1 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) -set(RELEASE_DATE "2023-09-21") + +string(TIMESTAMP CURRENT_DATE "%Y-%m-%d") +set(RELEASE_DATE "${CURRENT_DATE}") + set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") diff --git a/deploy/data/linux/AmneziaVPN.png b/deploy/data/linux/AmneziaVPN.png new file mode 100644 index 0000000000000000000000000000000000000000..0f104da2a56e789757d4fc5f8267106827a49a6c GIT binary patch literal 28646 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�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 S1R^Z}z(CjdTBWvQ)c*j9;LBG4 literal 0 HcmV?d00001 diff --git a/deploy/data/linux/client/share/applications/AmneziaVPN.desktop b/deploy/data/linux/client/share/applications/AmneziaVPN.desktop deleted file mode 100755 index d89252c0..00000000 --- a/deploy/data/linux/client/share/applications/AmneziaVPN.desktop +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env xdg-open -[Desktop Entry] -Type=Application -Name=AmneziaVPN client -Version=2.0.10 -Comment=Client of your self-hosted VPN -Exec=AmneziaVPN -Icon=/usr/share/pixmaps/AmneziaVPN_Logo.png -Categories=Network;Qt;Security; -Terminal=false diff --git a/deploy/data/linux/client/share/icons/AmneziaVPN_Logo.png b/deploy/data/linux/client/share/icons/AmneziaVPN_Logo.png deleted file mode 100755 index 0e281afb9b3daa1307c9756ebc38478d67efd262..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 56043 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 diff --git a/deploy/data/linux/post_install.sh b/deploy/data/linux/post_install.sh index f69de3d5..b3345bac 100755 --- a/deploy/data/linux/post_install.sh +++ b/deploy/data/linux/post_install.sh @@ -37,6 +37,7 @@ sudo ln -s $APP_PATH/client/$APP_NAME.sh /usr/local/bin/$APP_NAME >> $LOG_FILE echo "user desktop creation loop started" >> $LOG_FILE sudo cp $APP_PATH/$APP_NAME.desktop /usr/share/applications/ >> $LOG_FILE +sudo cp $APP_PATH/$APP_NAME.png /usr/share/pixmaps/ >> $LOG_FILE sudo chmod 555 /usr/share/applications/$APP_NAME.desktop >> $LOG_FILE echo "user desktop creation loop ended" >> $LOG_FILE diff --git a/deploy/data/linux/post_uninstall.sh b/deploy/data/linux/post_uninstall.sh index 029bb7cf..5849a90e 100755 --- a/deploy/data/linux/post_uninstall.sh +++ b/deploy/data/linux/post_uninstall.sh @@ -54,6 +54,11 @@ if test -f /usr/share/applications/$APP_NAME.desktop; then fi +if test -f /usr/share/pixmaps/$APP_NAME.png; then + sudo rm -f /usr/share/pixmaps/$APP_NAME.png >> $LOG_FILE + +fi + date >> $LOG_FILE echo "Service after uninstall status:" >> $LOG_FILE sudo systemctl status $APP_NAME >> $LOG_FILE diff --git a/deploy/installer/config.cmake b/deploy/installer/config.cmake index 7cc75153..13f09986 100644 --- a/deploy/installer/config.cmake +++ b/deploy/installer/config.cmake @@ -15,6 +15,11 @@ elseif(LINUX) ${CMAKE_CURRENT_LIST_DIR}/config/linux.xml.in ${CMAKE_BINARY_DIR}/installer/config/linux.xml ) + + configure_file( + ${CMAKE_CURRENT_LIST_DIR}/config/AmneziaVPN.desktop.in + ${CMAKE_BINARY_DIR}/../AppDir/AmneziaVPN.desktop + ) endif() configure_file( diff --git a/deploy/data/linux/AmneziaVPN.desktop b/deploy/installer/config/AmneziaVPN.desktop.in similarity index 64% rename from deploy/data/linux/AmneziaVPN.desktop rename to deploy/installer/config/AmneziaVPN.desktop.in index d89252c0..2a53074e 100755 --- a/deploy/data/linux/AmneziaVPN.desktop +++ b/deploy/installer/config/AmneziaVPN.desktop.in @@ -1,10 +1,10 @@ #!/usr/bin/env xdg-open [Desktop Entry] Type=Application -Name=AmneziaVPN client -Version=2.0.10 +Name=AmneziaVPN +Version=@CMAKE_PROJECT_VERSION@ Comment=Client of your self-hosted VPN Exec=AmneziaVPN -Icon=/usr/share/pixmaps/AmneziaVPN_Logo.png +Icon=/usr/share/pixmaps/AmneziaVPN.png Categories=Network;Qt;Security; Terminal=false diff --git a/deploy/installer/config/linux.xml.in b/deploy/installer/config/linux.xml.in index 150c9cc5..a39edbe4 100644 --- a/deploy/installer/config/linux.xml.in +++ b/deploy/installer/config/linux.xml.in @@ -1,7 +1,7 @@ AmneziaVPN - 1.6.0.0 + @CMAKE_PROJECT_VERSION@ AmneziaVPN AmneziaVPN AmneziaVPN From 7ede1a8d839f7985ad1e8b6ce9869a08aa84b98a Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Sun, 24 Sep 2023 22:09:30 +0300 Subject: [PATCH 139/278] Remove unused binary from build files --- client/CMakeLists.txt | 11 ----------- deploy/build_windows.bat | 1 - 2 files changed, 12 deletions(-) diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index ca5161cf..63810ed3 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -322,16 +322,5 @@ if(NOT IOS AND NOT ANDROID) endif() -if(WIN32) - add_custom_command( - TARGET ${PROJECT} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E $,copy,true> - $/../service/wireguard-service/wireguard-service.exe - $/wireguard/wireguard-service.exe - COMMAND_EXPAND_LISTS - ) -endif() - - target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC}) qt_finalize_target(${PROJECT}) diff --git a/deploy/build_windows.bat b/deploy/build_windows.bat index 7ae3e9f6..c4b7b8cf 100644 --- a/deploy/build_windows.bat +++ b/deploy/build_windows.bat @@ -68,7 +68,6 @@ signtool sign /v /n "Privacy Technologies OU" /fd sha256 /tr http://timestamp.co echo "Copying deploy data..." xcopy %DEPLOY_DATA_DIR% %OUT_APP_DIR% /s /e /y /i /f xcopy %PREBILT_DEPLOY_DATA_DIR% %OUT_APP_DIR% /s /e /y /i /f -copy "%WORK_DIR:"=%\service\wireguard-service\release\wireguard-service.exe" %OUT_APP_DIR%\wireguard\ cd %SCRIPT_DIR% xcopy %SCRIPT_DIR:"=%\installer %WORK_DIR:"=%\installer /s /e /y /i /f From 51497d87e01c5a7296a3b805e890a3e9e7b46dfd Mon Sep 17 00:00:00 2001 From: ronoaer Date: Mon, 25 Sep 2023 07:22:11 +0800 Subject: [PATCH 140/278] redirected to pagesetupwizardeasy when none containers installed --- client/ui/models/containers_model.cpp | 14 ++++++++++++++ client/ui/models/containers_model.h | 2 +- client/ui/qml/Components/ConnectButton.qml | 8 ++++++++ client/ui/qml/Pages2/PageSetupWizardEasy.qml | 2 ++ 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index afff44b6..6cf855a6 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -214,6 +214,20 @@ bool ContainersModel::isAmneziaDnsContainerInstalled(const int serverIndex) return containers.contains(DockerContainer::Dns); } +bool ContainersModel::isAnyContainerInstalled() +{ + for (int row=0; row < rowCount(); row++) { + QModelIndex idx = this->index(row, 0); + + if (this->data(idx, IsInstalledRole).toBool() && + this->data(idx, ServiceTypeRole).toInt() == ServiceType::Vpn) { + return true; + } + } + + return false; +} + QHash ContainersModel::roleNames() const { QHash roles; diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index 741a0620..2cc41cbf 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -63,7 +63,7 @@ public slots: bool isAmneziaDnsContainerInstalled(); bool isAmneziaDnsContainerInstalled(const int serverIndex); - // bool isOnlyServicesInstalled(const int serverIndex); + bool isAnyContainerInstalled(); protected: QHash roleNames() const override; diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml index ab28d0d0..b7484c73 100644 --- a/client/ui/qml/Components/ConnectButton.qml +++ b/client/ui/qml/Components/ConnectButton.qml @@ -5,6 +5,7 @@ import QtQuick.Shapes import Qt5Compat.GraphicalEffects import ConnectionState 1.0 +import PageEnum 1.0 Button { id: root @@ -137,6 +138,13 @@ Button { } onClicked: { + if (!ContainersModel.isAnyContainerInstalled()) { + ServersModel.currentlyProcessedIndex = ServersModel.getDefaultServerIndex() + PageController.goToPage(PageEnum.PageSetupWizardEasy) + + return + } + if (ConnectionController.isConnectionInProgress) { ConnectionController.closeConnection() } else if (ConnectionController.isConnected) { diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index b228a7a3..4f94e985 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -183,6 +183,8 @@ PageType { textColor: "#D7D8DB" borderWidth: 1 + visible: ContainersModel.isAnyContainerInstalled() + text: qsTr("Set up later") onClicked: function() { From 8b08a5bee0c6c13801ac331a93115738fb21cc97 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Mon, 25 Sep 2023 22:16:59 +0800 Subject: [PATCH 141/278] added border hover effect for textarea --- .../qml/Controls2/TextFieldWithHeaderType.qml | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml index 3f80428e..3a3f5a1a 100644 --- a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -30,6 +30,7 @@ Item { property string backgroundColor: "#1c1d21" property string backgroundDisabledColor: "transparent" + property string bgBorderHoveredColor: "#494B50" implicitWidth: content.implicitWidth implicitHeight: content.implicitHeight @@ -44,7 +45,7 @@ Item { Layout.preferredHeight: input.implicitHeight color: root.enabled ? root.backgroundColor : root.backgroundDisabledColor radius: 16 - border.color: textField.focus ? root.borderFocusedColor : root.borderColor + border.color: getBackgroundBorderColor(root.borderColor) border.width: 1 Behavior on border.color { @@ -102,12 +103,17 @@ Item { anchors.fill: parent acceptedButtons: Qt.RightButton onClicked: contextMenu.open() + enabled: true } ContextMenuType { id: contextMenu textObj: textField } + + onFocusChanged: { + backgroud.border.color = getBackgroundBorderColor(root.borderColor) + } } } @@ -149,11 +155,28 @@ Item { MouseArea { anchors.fill: root - cursorShape: Qt.PointingHandCursor + cursorShape: Qt.IBeamCursor + + hoverEnabled: true onPressed: function(mouse) { textField.forceActiveFocus() mouse.accepted = false + + backgroud.border.color = getBackgroundBorderColor(root.borderColor) + } + + onEntered: { + backgroud.border.color = getBackgroundBorderColor(bgBorderHoveredColor) + } + + + onExited: { + backgroud.border.color = getBackgroundBorderColor(root.borderColor) } } + + function getBackgroundBorderColor(noneFocusedColor) { + return textField.focus ? root.borderFocusedColor : noneFocusedColor + } } From ee99565b63c8ba4a96f6bf6bfc6b47d407afa64a Mon Sep 17 00:00:00 2001 From: ronoaer Date: Tue, 26 Sep 2023 16:38:08 +0800 Subject: [PATCH 142/278] 1. added border-hover-interaction 2. updated textarea-focus-interaction --- client/ui/qml/Controls2/TextAreaType.qml | 122 ++++++++++++++--------- 1 file changed, 75 insertions(+), 47 deletions(-) diff --git a/client/ui/qml/Controls2/TextAreaType.qml b/client/ui/qml/Controls2/TextAreaType.qml index 4b70c274..f4f75417 100644 --- a/client/ui/qml/Controls2/TextAreaType.qml +++ b/client/ui/qml/Controls2/TextAreaType.qml @@ -9,71 +9,99 @@ Rectangle { property alias textArea: textArea property alias textAreaText: textArea.text + property string borderHoveredColor: "#494B50" + property string borderNormalColor: "#2C2D30" + property string borderFocusedColor: "#d7d8db" + height: 148 color: "#1C1D21" border.width: 1 - border.color: "#2C2D30" + border.color: getBorderColor(borderNormalColor) radius: 16 - FlickableType { - id: fl - interactive: false + MouseArea { + id: parentMouse + anchors.fill: parent + cursorShape: Qt.IBeamCursor + onClicked: textArea.forceActiveFocus() + hoverEnabled: true - anchors.top: parent.top - anchors.bottom: parent.bottom - contentHeight: textArea.implicitHeight - TextArea { - id: textArea + FlickableType { + id: fl + interactive: false - width: parent.width + anchors.top: parent.top + anchors.bottom: parent.bottom + contentHeight: textArea.implicitHeight + TextArea { + id: textArea - topPadding: 16 - leftPadding: 16 - anchors.topMargin: 16 - anchors.bottomMargin: 16 + width: parent.width - color: "#D7D8DB" - selectionColor: "#633303" - selectedTextColor: "#D7D8DB" - placeholderTextColor: "#878B91" + topPadding: 16 + leftPadding: 16 + anchors.topMargin: 16 + anchors.bottomMargin: 16 - font.pixelSize: 16 - font.weight: Font.Medium - font.family: "PT Root UI VF" + color: "#D7D8DB" + selectionColor: "#633303" + selectedTextColor: "#D7D8DB" + placeholderTextColor: "#878B91" - placeholderText: root.placeholderText - text: root.text + font.pixelSize: 16 + font.weight: Font.Medium + font.family: "PT Root UI VF" - onCursorVisibleChanged: { - if (textArea.cursorVisible) { - fl.interactive = true - } else { - fl.interactive = false + placeholderText: root.placeholderText + text: root.text + + onCursorVisibleChanged: { + if (textArea.cursorVisible) { + fl.interactive = true + } else { + fl.interactive = false + } + } + + wrapMode: Text.Wrap + + MouseArea { + id: textAreaMouse + anchors.fill: parent + acceptedButtons: Qt.RightButton + hoverEnabled: true + onClicked: { + fl.interactive = true + contextMenu.open() + } + } + + onFocusChanged: { + root.border.color = getBorderColor(borderNormalColor) + } + + ContextMenuType { + id: contextMenu + textObj: textArea } } + } - wrapMode: Text.Wrap + onPressed: { + root.border.color = getBorderColor(borderFocusedColor) + } - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.RightButton - onClicked: { - fl.interactive = true - contextMenu.open() - } - } + onExited: { + root.border.color = getBorderColor(borderNormalColor) + } - ContextMenuType { - id: contextMenu - textObj: textArea - } + onEntered: { + root.border.color = getBorderColor(borderHoveredColor) } } - //todo make whole background clickable, with code below we lose ability to select text by mouse -// MouseArea { -// anchors.fill: parent -// cursorShape: Qt.IBeamCursor -// onClicked: textArea.forceActiveFocus() -// } + + function getBorderColor(noneFocusedColor) { + return textArea.focus ? root.borderFocusedColor : noneFocusedColor + } } From 37024eb91da6c3984c59f90a8cec3a3344693e22 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Tue, 26 Sep 2023 17:23:35 +0800 Subject: [PATCH 143/278] updated cursorshape of cardtype to Qt.PointingHandCursor --- client/ui/qml/Controls2/CardType.qml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/client/ui/qml/Controls2/CardType.qml b/client/ui/qml/Controls2/CardType.qml index 4b94cb1a..8429acb8 100644 --- a/client/ui/qml/Controls2/CardType.qml +++ b/client/ui/qml/Controls2/CardType.qml @@ -123,4 +123,11 @@ RadioButton { Layout.bottomMargin: 16 } } + + MouseArea { + anchors.fill: parent + + cursorShape: Qt.PointingHandCursor + enabled: false + } } From af53c456ea91a187defc72844e4a23de26d92987 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 27 Sep 2023 00:40:01 +0500 Subject: [PATCH 144/278] added passing new wireguard config parameters over uapi and configuring the amneziawireguard container --- .../amneziaWireGuardConfigurator.cpp | 51 +++++++++++++++++-- client/configurators/vpn_configurator.cpp | 6 ++- client/configurators/vpn_configurator.h | 4 +- .../configurators/wireguard_configurator.cpp | 43 ++++++++-------- client/configurators/wireguard_configurator.h | 9 +++- client/core/scripts_registry.cpp | 3 +- client/core/scripts_registry.h | 3 +- client/core/servercontroller.cpp | 31 +++++++++++ client/daemon/daemon.cpp | 11 ++++ client/daemon/interfaceconfig.h | 10 ++++ client/mozilla/localsocketcontroller.cpp | 17 ++++++- .../macos/daemon/wireguardutilsmacos.cpp | 11 ++++ client/protocols/protocols_defs.h | 30 +++++++++++ client/resources.qrc | 5 ++ .../amnezia_wireguard/Dockerfile | 46 +++++++++++++++++ .../amnezia_wireguard/configure_container.sh | 26 ++++++++++ .../amnezia_wireguard/run_container.sh | 18 +++++++ .../server_scripts/amnezia_wireguard/start.sh | 28 ++++++++++ .../amnezia_wireguard/template.conf | 20 ++++++++ 19 files changed, 342 insertions(+), 30 deletions(-) create mode 100644 client/server_scripts/amnezia_wireguard/Dockerfile create mode 100644 client/server_scripts/amnezia_wireguard/configure_container.sh create mode 100644 client/server_scripts/amnezia_wireguard/run_container.sh create mode 100644 client/server_scripts/amnezia_wireguard/start.sh create mode 100644 client/server_scripts/amnezia_wireguard/template.conf diff --git a/client/configurators/amneziaWireGuardConfigurator.cpp b/client/configurators/amneziaWireGuardConfigurator.cpp index 56f0c68e..3ed27208 100644 --- a/client/configurators/amneziaWireGuardConfigurator.cpp +++ b/client/configurators/amneziaWireGuardConfigurator.cpp @@ -1,7 +1,10 @@ #include "amneziaWireGuardConfigurator.h" +#include +#include + AmneziaWireGuardConfigurator::AmneziaWireGuardConfigurator(std::shared_ptr settings, QObject *parent) - : WireguardConfigurator(settings, parent) + : WireguardConfigurator(settings, true, parent) { } @@ -9,7 +12,49 @@ QString AmneziaWireGuardConfigurator::genAmneziaWireGuardConfig(const ServerCred DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode) { - auto config = WireguardConfigurator::genWireguardConfig(credentials, container, containerConfig, errorCode); + QString config = WireguardConfigurator::genWireguardConfig(credentials, container, containerConfig, errorCode); - return config; + QJsonObject jsonConfig = QJsonDocument::fromJson(config.toUtf8()).object(); + QJsonObject awgConfig = containerConfig.value(config_key::amneziaWireguard).toObject(); + + auto junkPacketCount = + awgConfig.value(config_key::junkPacketCount).toString(protocols::amneziawireguard::defaultJunkPacketCount); + auto junkPacketMinSize = + awgConfig.value(config_key::junkPacketMinSize).toString(protocols::amneziawireguard::defaultJunkPacketMinSize); + auto junkPacketMaxSize = + awgConfig.value(config_key::junkPacketMaxSize).toString(protocols::amneziawireguard::defaultJunkPacketMaxSize); + auto initPacketJunkSize = + awgConfig.value(config_key::initPacketJunkSize).toString(protocols::amneziawireguard::defaultInitPacketJunkSize); + auto responsePacketJunkSize = + awgConfig.value(config_key::responsePacketJunkSize).toString(protocols::amneziawireguard::defaultResponsePacketJunkSize); + auto initPacketMagicHeader = + awgConfig.value(config_key::initPacketMagicHeader).toString(protocols::amneziawireguard::defaultInitPacketMagicHeader); + auto responsePacketMagicHeader = + awgConfig.value(config_key::responsePacketMagicHeader).toString(protocols::amneziawireguard::defaultResponsePacketMagicHeader); + auto underloadPacketMagicHeader = + awgConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::amneziawireguard::defaultUnderloadPacketMagicHeader); + auto transportPacketMagicHeader = + awgConfig.value(config_key::transportPacketMagicHeader).toString(protocols::amneziawireguard::defaultTransportPacketMagicHeader); + + config.replace("$JUNK_PACKET_COUNT", junkPacketCount); + config.replace("$JUNK_PACKET_MIN_SIZE", junkPacketMinSize); + config.replace("$JUNK_PACKET_MAX_SIZE", junkPacketMaxSize); + config.replace("$INIT_PACKET_JUNK_SIZE", initPacketJunkSize); + config.replace("$RESPONSE_PACKET_JUNK_SIZE", responsePacketJunkSize); + config.replace("$INIT_PACKET_MAGIC_HEADER", initPacketMagicHeader); + config.replace("$RESPONSE_PACKET_MAGIC_HEADER", responsePacketMagicHeader); + config.replace("$UNDERLOAD_PACKET_MAGIC_HEADER", underloadPacketMagicHeader); + config.replace("$TRANSPORT_PACKET_MAGIC_HEADER", transportPacketMagicHeader); + + jsonConfig[config_key::junkPacketCount] = junkPacketCount; + jsonConfig[config_key::junkPacketMinSize] = junkPacketMinSize; + jsonConfig[config_key::junkPacketMaxSize] = junkPacketMaxSize; + jsonConfig[config_key::initPacketJunkSize] = initPacketJunkSize; + jsonConfig[config_key::responsePacketJunkSize] = responsePacketJunkSize; + jsonConfig[config_key::initPacketMagicHeader] = initPacketMagicHeader; + jsonConfig[config_key::responsePacketMagicHeader] = responsePacketMagicHeader; + jsonConfig[config_key::underloadPacketMagicHeader] = underloadPacketMagicHeader; + jsonConfig[config_key::transportPacketMagicHeader] = transportPacketMagicHeader; + + return QJsonDocument(jsonConfig).toJson(); } diff --git a/client/configurators/vpn_configurator.cpp b/client/configurators/vpn_configurator.cpp index 7f0e95df..6706deed 100644 --- a/client/configurators/vpn_configurator.cpp +++ b/client/configurators/vpn_configurator.cpp @@ -5,6 +5,7 @@ #include "shadowsocks_configurator.h" #include "ssh_configurator.h" #include "wireguard_configurator.h" +#include "amneziaWireGuardConfigurator.h" #include #include @@ -20,9 +21,10 @@ VpnConfigurator::VpnConfigurator(std::shared_ptr settings, QObject *pa openVpnConfigurator = std::shared_ptr(new OpenVpnConfigurator(settings, this)); shadowSocksConfigurator = std::shared_ptr(new ShadowSocksConfigurator(settings, this)); cloakConfigurator = std::shared_ptr(new CloakConfigurator(settings, this)); - wireguardConfigurator = std::shared_ptr(new WireguardConfigurator(settings, this)); + wireguardConfigurator = std::shared_ptr(new WireguardConfigurator(settings, false, this)); ikev2Configurator = std::shared_ptr(new Ikev2Configurator(settings, this)); sshConfigurator = std::shared_ptr(new SshConfigurator(settings, this)); + amneziaWireGuardConfigurator = std::shared_ptr(new AmneziaWireGuardConfigurator(settings, this)); } QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container, @@ -41,7 +43,7 @@ QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentia return wireguardConfigurator->genWireguardConfig(credentials, container, containerConfig, errorCode); case Proto::AmneziaWireGuard: - return wireguardConfigurator->genWireguardConfig(credentials, container, containerConfig, errorCode); + return amneziaWireGuardConfigurator->genAmneziaWireGuardConfig(credentials, container, containerConfig, errorCode); case Proto::Ikev2: return ikev2Configurator->genIkev2Config(credentials, container, containerConfig, errorCode); diff --git a/client/configurators/vpn_configurator.h b/client/configurators/vpn_configurator.h index 3b9c761b..d304e4c3 100644 --- a/client/configurators/vpn_configurator.h +++ b/client/configurators/vpn_configurator.h @@ -13,13 +13,14 @@ class CloakConfigurator; class WireguardConfigurator; class Ikev2Configurator; class SshConfigurator; +class AmneziaWireGuardConfigurator; // Retrieve connection settings from server class VpnConfigurator : ConfiguratorBase { Q_OBJECT public: - VpnConfigurator(std::shared_ptr settings, QObject *parent = nullptr); + explicit VpnConfigurator(std::shared_ptr settings, QObject *parent = nullptr); QString genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, Proto proto, ErrorCode *errorCode = nullptr); @@ -40,6 +41,7 @@ public: std::shared_ptr wireguardConfigurator; std::shared_ptr ikev2Configurator; std::shared_ptr sshConfigurator; + std::shared_ptr amneziaWireGuardConfigurator; }; #endif // VPN_CONFIGURATOR_H diff --git a/client/configurators/wireguard_configurator.cpp b/client/configurators/wireguard_configurator.cpp index 02716b72..dd836a18 100644 --- a/client/configurators/wireguard_configurator.cpp +++ b/client/configurators/wireguard_configurator.cpp @@ -19,9 +19,17 @@ #include "settings.h" #include "utilities.h" -WireguardConfigurator::WireguardConfigurator(std::shared_ptr settings, QObject *parent) - : ConfiguratorBase(settings, parent) +WireguardConfigurator::WireguardConfigurator(std::shared_ptr settings, bool isAmneziaWireGuard, QObject *parent) + : ConfiguratorBase(settings, parent), m_isAmneziaWireGuard(isAmneziaWireGuard) { + m_serverConfigPath = m_isAmneziaWireGuard ? amnezia::protocols::amneziawireguard::serverConfigPath + : amnezia::protocols::wireguard::serverConfigPath; + m_serverPublicKeyPath = m_isAmneziaWireGuard ? amnezia::protocols::amneziawireguard::serverPublicKeyPath + : amnezia::protocols::wireguard::serverPublicKeyPath; + m_serverPskKeyPath = m_isAmneziaWireGuard ? amnezia::protocols::amneziawireguard::serverPskKeyPath + : amnezia::protocols::wireguard::serverPskKeyPath; + m_configTemplate = m_isAmneziaWireGuard ? ProtocolScriptType::amnezia_wireguard_template + : ProtocolScriptType::wireguard_template; } WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys() @@ -62,7 +70,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon { WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys(); connData.host = credentials.hostName; - connData.port = containerConfig.value(config_key::wireguard) + connData.port = containerConfig.value(m_isAmneziaWireGuard ? config_key::amneziaWireguard : config_key::wireguard) .toObject() .value(config_key::port) .toString(protocols::wireguard::defaultPort); @@ -79,7 +87,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon // Get list of already created clients (only IP addresses) QString nextIpNumber; { - QString script = QString("cat %1 | grep AllowedIPs").arg(amnezia::protocols::wireguard::serverConfigPath); + QString script = QString("cat %1 | grep AllowedIPs").arg(m_serverConfigPath); QString stdOut; auto cbReadStdOut = [&](const QString &data, libssh::Client &) { stdOut += data + "\n"; @@ -126,8 +134,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon } // Get keys - connData.serverPubKey = serverController.getTextFileFromContainer( - container, credentials, amnezia::protocols::wireguard::serverPublicKeyPath, &e); + connData.serverPubKey = serverController.getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, &e); connData.serverPubKey.replace("\n", ""); if (e) { if (errorCode) @@ -135,8 +142,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon return connData; } - connData.pskKey = serverController.getTextFileFromContainer(container, credentials, - amnezia::protocols::wireguard::serverPskKeyPath, &e); + connData.pskKey = serverController.getTextFileFromContainer(container, credentials, m_serverPskKeyPath, &e); connData.pskKey.replace("\n", ""); if (e) { @@ -150,12 +156,9 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon "PublicKey = %1\n" "PresharedKey = %2\n" "AllowedIPs = %3/32\n\n") - .arg(connData.clientPubKey) - .arg(connData.pskKey) - .arg(connData.clientIP); + .arg(connData.clientPubKey, connData.pskKey, connData.clientIP); - e = serverController.uploadTextFileToContainer(container, credentials, configPart, - protocols::wireguard::serverConfigPath, + e = serverController.uploadTextFileToContainer(container, credentials, configPart, m_serverConfigPath, libssh::SftpOverwriteMode::SftpAppendToExisting); if (e) { @@ -164,11 +167,11 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon return connData; } + QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'") + .arg(m_serverConfigPath); + e = serverController.runScript( - credentials, - serverController.replaceVars("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick " - "strip /opt/amnezia/wireguard/wg0.conf)'", - serverController.genVarsForScript(credentials, container))); + credentials, serverController.replaceVars(script, serverController.genVarsForScript(credentials, container))); return connData; } @@ -177,9 +180,9 @@ QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &crede const QJsonObject &containerConfig, ErrorCode *errorCode) { ServerController serverController(m_settings); - QString config = - serverController.replaceVars(amnezia::scriptData(ProtocolScriptType::wireguard_template, container), - serverController.genVarsForScript(credentials, container, containerConfig)); + QString scriptData = amnezia::scriptData(m_configTemplate, container); + QString config = serverController.replaceVars( + scriptData, serverController.genVarsForScript(credentials, container, containerConfig)); ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode); if (errorCode && *errorCode) { diff --git a/client/configurators/wireguard_configurator.h b/client/configurators/wireguard_configurator.h index 140acc47..70ed729b 100644 --- a/client/configurators/wireguard_configurator.h +++ b/client/configurators/wireguard_configurator.h @@ -6,12 +6,13 @@ #include "configurator_base.h" #include "core/defs.h" +#include "core/scripts_registry.h" class WireguardConfigurator : public ConfiguratorBase { Q_OBJECT public: - WireguardConfigurator(std::shared_ptr settings, QObject *parent = nullptr); + WireguardConfigurator(std::shared_ptr settings, bool isAmneziaWireGuard, QObject *parent = nullptr); struct ConnectionData { @@ -35,6 +36,12 @@ private: const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr); ConnectionData genClientKeys(); + + bool m_isAmneziaWireGuard; + QString m_serverConfigPath; + QString m_serverPublicKeyPath; + QString m_serverPskKeyPath; + amnezia::ProtocolScriptType m_configTemplate; }; #endif // WIREGUARD_CONFIGURATOR_H diff --git a/client/core/scripts_registry.cpp b/client/core/scripts_registry.cpp index 31508152..24deb41a 100644 --- a/client/core/scripts_registry.cpp +++ b/client/core/scripts_registry.cpp @@ -11,7 +11,7 @@ QString amnezia::scriptFolder(amnezia::DockerContainer container) case DockerContainer::Cloak: return QLatin1String("openvpn_cloak"); case DockerContainer::ShadowSocks: return QLatin1String("openvpn_shadowsocks"); case DockerContainer::WireGuard: return QLatin1String("wireguard"); - case DockerContainer::AmneziaWireGuard: return QLatin1String("wireguard"); + case DockerContainer::AmneziaWireGuard: return QLatin1String("amnezia_wireguard"); case DockerContainer::Ipsec: return QLatin1String("ipsec"); case DockerContainer::TorWebSite: return QLatin1String("website_tor"); @@ -46,6 +46,7 @@ QString amnezia::scriptName(ProtocolScriptType type) case ProtocolScriptType::container_startup: return QLatin1String("start.sh"); case ProtocolScriptType::openvpn_template: return QLatin1String("template.ovpn"); case ProtocolScriptType::wireguard_template: return QLatin1String("template.conf"); + case ProtocolScriptType::amnezia_wireguard_template: return QLatin1String("template.conf"); } } diff --git a/client/core/scripts_registry.h b/client/core/scripts_registry.h index b30be2ff..5c7a1b6a 100644 --- a/client/core/scripts_registry.h +++ b/client/core/scripts_registry.h @@ -26,7 +26,8 @@ enum ProtocolScriptType { configure_container, container_startup, openvpn_template, - wireguard_template + wireguard_template, + amnezia_wireguard_template }; diff --git a/client/core/servercontroller.cpp b/client/core/servercontroller.cpp index 27213dc3..3b30451f 100644 --- a/client/core/servercontroller.cpp +++ b/client/core/servercontroller.cpp @@ -584,6 +584,37 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential vars.append({ { "$SFTP_USER", sftpConfig.value(config_key::userName).toString() } }); vars.append({ { "$SFTP_PASSWORD", sftpConfig.value(config_key::password).toString() } }); + // Amnezia wireguard vars + vars.append({ { "$AMNEZIAWIREGUARD_SERVER_PORT", + amneziaWireguarConfig.value(config_key::port).toString(protocols::amneziawireguard::defaultPort) } }); + vars.append({ { "$JUNK_PACKET_COUNT", + amneziaWireguarConfig.value(config_key::junkPacketCount) + .toString(protocols::amneziawireguard::defaultJunkPacketCount) } }); + vars.append({ { "$JUNK_PACKET_MIN_SIZE", + amneziaWireguarConfig.value(config_key::junkPacketMinSize) + .toString(protocols::amneziawireguard::defaultJunkPacketMinSize) } }); + vars.append({ { "$JUNK_PACKET_MAX_SIZE", + amneziaWireguarConfig.value(config_key::junkPacketMaxSize) + .toString(protocols::amneziawireguard::defaultJunkPacketMaxSize) } }); + vars.append({ { "$INIT_PACKET_JUNK_SIZE", + amneziaWireguarConfig.value(config_key::initPacketJunkSize) + .toString(protocols::amneziawireguard::defaultInitPacketJunkSize) } }); + vars.append({ { "$RESPONSE_PACKET_JUNK_SIZE", + amneziaWireguarConfig.value(config_key::responsePacketJunkSize) + .toString(protocols::amneziawireguard::defaultResponsePacketJunkSize) } }); + vars.append({ { "$INIT_PACKET_MAGIC_HEADER", + amneziaWireguarConfig.value(config_key::initPacketMagicHeader) + .toString(protocols::amneziawireguard::defaultInitPacketMagicHeader) } }); + vars.append({ { "$RESPONSE_PACKET_MAGIC_HEADER", + amneziaWireguarConfig.value(config_key::responsePacketMagicHeader) + .toString(protocols::amneziawireguard::defaultResponsePacketMagicHeader) } }); + vars.append({ { "$UNDERLOAD_PACKET_MAGIC_HEADER", + amneziaWireguarConfig.value(config_key::underloadPacketMagicHeader) + .toString(protocols::amneziawireguard::defaultUnderloadPacketMagicHeader) } }); + vars.append({ { "$TRANSPORT_PACKET_MAGIC_HEADER", + amneziaWireguarConfig.value(config_key::transportPacketMagicHeader) + .toString(protocols::amneziawireguard::defaultTransportPacketMagicHeader) } }); + QString serverIp = Utils::getIPAddress(credentials.hostName); if (!serverIp.isEmpty()) { vars.append({ { "$SERVER_IP_ADDRESS", serverIp } }); diff --git a/client/daemon/daemon.cpp b/client/daemon/daemon.cpp index 3a0dc4d9..13310951 100644 --- a/client/daemon/daemon.cpp +++ b/client/daemon/daemon.cpp @@ -359,6 +359,17 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) { if (!parseStringList(obj, "vpnDisabledApps", config.m_vpnDisabledApps)) { return false; } + + config.m_junkPacketCount = obj.value("Jc").toString(); + config.m_junkPacketMinSize = obj.value("Jmin").toString(); + config.m_junkPacketMaxSize = obj.value("Jmax").toString(); + config.m_initPacketJunkSize = obj.value("S1").toString(); + config.m_responsePacketJunkSize = obj.value("S2").toString(); + config.m_initPacketMagicHeader = obj.value("H1").toString(); + config.m_responsePacketMagicHeader = obj.value("H2").toString(); + config.m_underloadPacketMagicHeader = obj.value("H3").toString(); + config.m_transportPacketMagicHeader = obj.value("H4").toString(); + return true; } diff --git a/client/daemon/interfaceconfig.h b/client/daemon/interfaceconfig.h index 61ffdd83..29aef085 100644 --- a/client/daemon/interfaceconfig.h +++ b/client/daemon/interfaceconfig.h @@ -40,6 +40,16 @@ class InterfaceConfig { QString m_installationId; #endif + QString m_junkPacketCount; + QString m_junkPacketMinSize; + QString m_junkPacketMaxSize; + QString m_initPacketJunkSize; + QString m_responsePacketJunkSize; + QString m_initPacketMagicHeader; + QString m_responsePacketMagicHeader; + QString m_underloadPacketMagicHeader; + QString m_transportPacketMagicHeader; + QJsonObject toJson() const; QString toWgConf( const QMap& extra = QMap()) const; diff --git a/client/mozilla/localsocketcontroller.cpp b/client/mozilla/localsocketcontroller.cpp index 40bc0bba..c9fa6a42 100644 --- a/client/mozilla/localsocketcontroller.cpp +++ b/client/mozilla/localsocketcontroller.cpp @@ -115,7 +115,9 @@ void LocalSocketController::daemonConnected() { } void LocalSocketController::activate(const QJsonObject &rawConfig) { - QJsonObject wgConfig = rawConfig.value("wireguard_config_data").toObject(); + QString protocolName = rawConfig.value("protocol").toString(); + + QJsonObject wgConfig = rawConfig.value(protocolName + "_config_data").toObject(); QJsonObject json; json.insert("type", "activate"); @@ -160,6 +162,19 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) { // splitTunnelApps.append(QJsonValue(uri)); // } // json.insert("vpnDisabledApps", splitTunnelApps); + + if (protocolName == amnezia::config_key::amneziaWireguard) { + json.insert(amnezia::config_key::junkPacketCount, wgConfig.value(amnezia::config_key::junkPacketCount)); + json.insert(amnezia::config_key::junkPacketMinSize, wgConfig.value(amnezia::config_key::junkPacketMinSize)); + json.insert(amnezia::config_key::junkPacketMaxSize, wgConfig.value(amnezia::config_key::junkPacketMaxSize)); + json.insert(amnezia::config_key::initPacketJunkSize, wgConfig.value(amnezia::config_key::initPacketJunkSize)); + json.insert(amnezia::config_key::responsePacketJunkSize, wgConfig.value(amnezia::config_key::responsePacketJunkSize)); + json.insert(amnezia::config_key::initPacketMagicHeader, wgConfig.value(amnezia::config_key::initPacketMagicHeader)); + json.insert(amnezia::config_key::responsePacketMagicHeader, wgConfig.value(amnezia::config_key::responsePacketMagicHeader)); + json.insert(amnezia::config_key::underloadPacketMagicHeader, wgConfig.value(amnezia::config_key::underloadPacketMagicHeader)); + json.insert(amnezia::config_key::transportPacketMagicHeader, wgConfig.value(amnezia::config_key::transportPacketMagicHeader)); + } + write(json); } diff --git a/client/platforms/macos/daemon/wireguardutilsmacos.cpp b/client/platforms/macos/daemon/wireguardutilsmacos.cpp index 1f422462..ead53e23 100644 --- a/client/platforms/macos/daemon/wireguardutilsmacos.cpp +++ b/client/platforms/macos/daemon/wireguardutilsmacos.cpp @@ -163,6 +163,17 @@ bool WireguardUtilsMacos::updatePeer(const InterfaceConfig& config) { out << "allowed_ip=" << ip.toString() << "\n"; } + + out << "Jc=" << config.m_junkPacketCount << "\n"; + out << "jmin=" << config.m_junkPacketMinSize << "\n"; + out << "jmax=" << config.m_junkPacketMaxSize << "\n"; + out << "s1=" << config.m_initPacketJunkSize << "\n"; + out << "s2=" << config.m_responsePacketJunkSize << "\n"; + out << "h1=" << config.m_initPacketMagicHeader << "\n"; + out << "h2=" << config.m_responsePacketMagicHeader << "\n"; + out << "h3=" << config.m_underloadPacketMagicHeader << "\n"; + out << "h4=" << config.m_transportPacketMagicHeader << "\n"; + // Exclude the server address, except for multihop exit servers. if ((config.m_hopType != InterfaceConfig::MultiHopExit) && (m_rtmonitor != nullptr)) { diff --git a/client/protocols/protocols_defs.h b/client/protocols/protocols_defs.h index 4e72e318..e26e60a4 100644 --- a/client/protocols/protocols_defs.h +++ b/client/protocols/protocols_defs.h @@ -61,11 +61,22 @@ namespace amnezia constexpr char isThirdPartyConfig[] = "isThirdPartyConfig"; + constexpr char junkPacketCount[] = "Jc"; + constexpr char junkPacketMinSize[] = "Jmin"; + constexpr char junkPacketMaxSize[] = "Jmax"; + constexpr char initPacketJunkSize[] = "S1"; + constexpr char responsePacketJunkSize[] = "S2"; + constexpr char initPacketMagicHeader[] = "H1"; + constexpr char responsePacketMagicHeader[] = "H2"; + constexpr char underloadPacketMagicHeader[] = "H3"; + constexpr char transportPacketMagicHeader[] = "H4"; + constexpr char openvpn[] = "openvpn"; constexpr char wireguard[] = "wireguard"; constexpr char shadowsocks[] = "shadowsocks"; constexpr char cloak[] = "cloak"; constexpr char sftp[] = "sftp"; + constexpr char amneziaWireguard[] = "amneziawireguard"; } @@ -140,6 +151,25 @@ namespace amnezia } // namespace sftp + namespace amneziawireguard + { + constexpr char defaultPort[] = "55424"; + + constexpr char serverConfigPath[] = "/opt/amnezia/amneziawireguard/wg0.conf"; + constexpr char serverPublicKeyPath[] = "/opt/amnezia/amneziawireguard/wireguard_server_public_key.key"; + constexpr char serverPskKeyPath[] = "/opt/amnezia/amneziawireguard/wireguard_psk.key"; + + constexpr char defaultJunkPacketCount[] = "3"; + constexpr char defaultJunkPacketMinSize[] = "10"; + constexpr char defaultJunkPacketMaxSize[] = "30"; + constexpr char defaultInitPacketJunkSize[] = "15"; + constexpr char defaultResponsePacketJunkSize[] = "18"; + constexpr char defaultInitPacketMagicHeader[] = "1020325451"; + constexpr char defaultResponsePacketMagicHeader[] = "3288052141"; + constexpr char defaultTransportPacketMagicHeader[] = "2528465083"; + constexpr char defaultUnderloadPacketMagicHeader[] = "1766607858"; + } + } // namespace protocols namespace ProtocolEnumNS diff --git a/client/resources.qrc b/client/resources.qrc index 44c61172..b79ed3d2 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -216,5 +216,10 @@ ui/qml/Pages2/PageServiceDnsSettings.qml ui/qml/Controls2/TopCloseButtonType.qml ui/qml/Pages2/PageProtocolAmneziaWireGuardSettings.qml + server_scripts/amnezia_wireguard/template.conf + server_scripts/amnezia_wireguard/start.sh + server_scripts/amnezia_wireguard/configure_container.sh + server_scripts/amnezia_wireguard/run_container.sh + server_scripts/amnezia_wireguard/Dockerfile diff --git a/client/server_scripts/amnezia_wireguard/Dockerfile b/client/server_scripts/amnezia_wireguard/Dockerfile new file mode 100644 index 00000000..ed974dc6 --- /dev/null +++ b/client/server_scripts/amnezia_wireguard/Dockerfile @@ -0,0 +1,46 @@ +FROM amneziavpn/amnezia-wg:latest + +LABEL maintainer="AmneziaVPN" + +#Install required packages +RUN apk add --no-cache curl wireguard-tools dumb-init +RUN apk --update upgrade --no-cache + +RUN mkdir -p /opt/amnezia +RUN echo -e "#!/bin/bash\ntail -f /dev/null" > /opt/amnezia/start.sh +RUN chmod a+x /opt/amnezia/start.sh + +# Tune network +RUN echo -e " \n\ + fs.file-max = 51200 \n\ + \n\ + net.core.rmem_max = 67108864 \n\ + net.core.wmem_max = 67108864 \n\ + net.core.netdev_max_backlog = 250000 \n\ + net.core.somaxconn = 4096 \n\ + \n\ + net.ipv4.tcp_syncookies = 1 \n\ + net.ipv4.tcp_tw_reuse = 1 \n\ + net.ipv4.tcp_tw_recycle = 0 \n\ + net.ipv4.tcp_fin_timeout = 30 \n\ + net.ipv4.tcp_keepalive_time = 1200 \n\ + net.ipv4.ip_local_port_range = 10000 65000 \n\ + net.ipv4.tcp_max_syn_backlog = 8192 \n\ + net.ipv4.tcp_max_tw_buckets = 5000 \n\ + net.ipv4.tcp_fastopen = 3 \n\ + net.ipv4.tcp_mem = 25600 51200 102400 \n\ + net.ipv4.tcp_rmem = 4096 87380 67108864 \n\ + net.ipv4.tcp_wmem = 4096 65536 67108864 \n\ + net.ipv4.tcp_mtu_probing = 1 \n\ + net.ipv4.tcp_congestion_control = hybla \n\ + # for low-latency network, use cubic instead \n\ + # net.ipv4.tcp_congestion_control = cubic \n\ + " | sed -e 's/^\s\+//g' | tee -a /etc/sysctl.conf && \ + mkdir -p /etc/security && \ + echo -e " \n\ + * soft nofile 51200 \n\ + * hard nofile 51200 \n\ + " | sed -e 's/^\s\+//g' | tee -a /etc/security/limits.conf + +ENTRYPOINT [ "dumb-init", "/opt/amnezia/start.sh" ] +CMD [ "" ] diff --git a/client/server_scripts/amnezia_wireguard/configure_container.sh b/client/server_scripts/amnezia_wireguard/configure_container.sh new file mode 100644 index 00000000..8653a932 --- /dev/null +++ b/client/server_scripts/amnezia_wireguard/configure_container.sh @@ -0,0 +1,26 @@ +mkdir -p /opt/amnezia/amneziawireguard +cd /opt/amnezia/amneziawireguard +WIREGUARD_SERVER_PRIVATE_KEY=$(wg genkey) +echo $WIREGUARD_SERVER_PRIVATE_KEY > /opt/amnezia/amneziawireguard/wireguard_server_private_key.key + +WIREGUARD_SERVER_PUBLIC_KEY=$(echo $WIREGUARD_SERVER_PRIVATE_KEY | wg pubkey) +echo $WIREGUARD_SERVER_PUBLIC_KEY > /opt/amnezia/amneziawireguard/wireguard_server_public_key.key + +WIREGUARD_PSK=$(wg genpsk) +echo $WIREGUARD_PSK > /opt/amnezia/amneziawireguard/wireguard_psk.key + +cat > /opt/amnezia/amneziawireguard/wg0.conf < Date: Wed, 27 Sep 2023 00:45:42 +0500 Subject: [PATCH 145/278] added passing new amneziawireguard config parameters over uapi for all platforms --- client/platforms/linux/daemon/wireguardutilslinux.cpp | 10 ++++++++++ client/platforms/macos/daemon/wireguardutilsmacos.cpp | 2 +- .../platforms/windows/daemon/wireguardutilswindows.cpp | 10 ++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/client/platforms/linux/daemon/wireguardutilslinux.cpp b/client/platforms/linux/daemon/wireguardutilslinux.cpp index a8b7b04a..dbb92f61 100644 --- a/client/platforms/linux/daemon/wireguardutilslinux.cpp +++ b/client/platforms/linux/daemon/wireguardutilslinux.cpp @@ -161,6 +161,16 @@ bool WireguardUtilsLinux::updatePeer(const InterfaceConfig& config) { out << "allowed_ip=" << ip.toString() << "\n"; } + out << "jc=" << config.m_junkPacketCount << "\n"; + out << "jmin=" << config.m_junkPacketMinSize << "\n"; + out << "jmax=" << config.m_junkPacketMaxSize << "\n"; + out << "s1=" << config.m_initPacketJunkSize << "\n"; + out << "s2=" << config.m_responsePacketJunkSize << "\n"; + out << "h1=" << config.m_initPacketMagicHeader << "\n"; + out << "h2=" << config.m_responsePacketMagicHeader << "\n"; + out << "h3=" << config.m_underloadPacketMagicHeader << "\n"; + out << "h4=" << config.m_transportPacketMagicHeader << "\n"; + // Exclude the server address, except for multihop exit servers. if ((config.m_hopType != InterfaceConfig::MultiHopExit) && (m_rtmonitor != nullptr)) { diff --git a/client/platforms/macos/daemon/wireguardutilsmacos.cpp b/client/platforms/macos/daemon/wireguardutilsmacos.cpp index ead53e23..2170d69e 100644 --- a/client/platforms/macos/daemon/wireguardutilsmacos.cpp +++ b/client/platforms/macos/daemon/wireguardutilsmacos.cpp @@ -164,7 +164,7 @@ bool WireguardUtilsMacos::updatePeer(const InterfaceConfig& config) { } - out << "Jc=" << config.m_junkPacketCount << "\n"; + out << "jc=" << config.m_junkPacketCount << "\n"; out << "jmin=" << config.m_junkPacketMinSize << "\n"; out << "jmax=" << config.m_junkPacketMaxSize << "\n"; out << "s1=" << config.m_initPacketJunkSize << "\n"; diff --git a/client/platforms/windows/daemon/wireguardutilswindows.cpp b/client/platforms/windows/daemon/wireguardutilswindows.cpp index 1e0a4752..21df2611 100644 --- a/client/platforms/windows/daemon/wireguardutilswindows.cpp +++ b/client/platforms/windows/daemon/wireguardutilswindows.cpp @@ -165,6 +165,16 @@ bool WireguardUtilsWindows::updatePeer(const InterfaceConfig& config) { out << "allowed_ip=" << ip.toString() << "\n"; } + out << "jc=" << config.m_junkPacketCount << "\n"; + out << "jmin=" << config.m_junkPacketMinSize << "\n"; + out << "jmax=" << config.m_junkPacketMaxSize << "\n"; + out << "s1=" << config.m_initPacketJunkSize << "\n"; + out << "s2=" << config.m_responsePacketJunkSize << "\n"; + out << "h1=" << config.m_initPacketMagicHeader << "\n"; + out << "h2=" << config.m_responsePacketMagicHeader << "\n"; + out << "h3=" << config.m_underloadPacketMagicHeader << "\n"; + out << "h4=" << config.m_transportPacketMagicHeader << "\n"; + // Exclude the server address, except for multihop exit servers. if (config.m_hopType != InterfaceConfig::MultiHopExit) { m_routeMonitor.addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn)); From 423305c35a49e6949a4f62664dc5a9cf2d474a19 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 28 Sep 2023 02:14:07 +0500 Subject: [PATCH 146/278] moved the configuration of new parameters for awg to addInterface() --- client/daemon/daemon.cpp | 20 +++++++++------- .../linux/daemon/wireguardutilslinux.cpp | 23 ++++++++++-------- .../macos/daemon/wireguardutilsmacos.cpp | 24 ++++++++++--------- .../windows/daemon/wireguardutilswindows.cpp | 10 -------- 4 files changed, 37 insertions(+), 40 deletions(-) diff --git a/client/daemon/daemon.cpp b/client/daemon/daemon.cpp index 13310951..63a5c7f6 100644 --- a/client/daemon/daemon.cpp +++ b/client/daemon/daemon.cpp @@ -360,15 +360,17 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) { return false; } - config.m_junkPacketCount = obj.value("Jc").toString(); - config.m_junkPacketMinSize = obj.value("Jmin").toString(); - config.m_junkPacketMaxSize = obj.value("Jmax").toString(); - config.m_initPacketJunkSize = obj.value("S1").toString(); - config.m_responsePacketJunkSize = obj.value("S2").toString(); - config.m_initPacketMagicHeader = obj.value("H1").toString(); - config.m_responsePacketMagicHeader = obj.value("H2").toString(); - config.m_underloadPacketMagicHeader = obj.value("H3").toString(); - config.m_transportPacketMagicHeader = obj.value("H4").toString(); + if (!obj.value("Jc").isNull()) { + config.m_junkPacketCount = obj.value("Jc").toString(); + config.m_junkPacketMinSize = obj.value("Jmin").toString(); + config.m_junkPacketMaxSize = obj.value("Jmax").toString(); + config.m_initPacketJunkSize = obj.value("S1").toString(); + config.m_responsePacketJunkSize = obj.value("S2").toString(); + config.m_initPacketMagicHeader = obj.value("H1").toString(); + config.m_responsePacketMagicHeader = obj.value("H2").toString(); + config.m_underloadPacketMagicHeader = obj.value("H3").toString(); + config.m_transportPacketMagicHeader = obj.value("H4").toString(); + } return true; } diff --git a/client/platforms/linux/daemon/wireguardutilslinux.cpp b/client/platforms/linux/daemon/wireguardutilslinux.cpp index dbb92f61..792120a7 100644 --- a/client/platforms/linux/daemon/wireguardutilslinux.cpp +++ b/client/platforms/linux/daemon/wireguardutilslinux.cpp @@ -100,6 +100,19 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) { QTextStream out(&message); out << "private_key=" << QString(privateKey.toHex()) << "\n"; out << "replace_peers=true\n"; + + if (config.m_junkPacketCount != "") { + out << "jc=" << config.m_junkPacketCount << "\n"; + out << "jmin=" << config.m_junkPacketMinSize << "\n"; + out << "jmax=" << config.m_junkPacketMaxSize << "\n"; + out << "s1=" << config.m_initPacketJunkSize << "\n"; + out << "s2=" << config.m_responsePacketJunkSize << "\n"; + out << "h1=" << config.m_initPacketMagicHeader << "\n"; + out << "h2=" << config.m_responsePacketMagicHeader << "\n"; + out << "h3=" << config.m_underloadPacketMagicHeader << "\n"; + out << "h4=" << config.m_transportPacketMagicHeader << "\n"; + } + int err = uapiErrno(uapiCommand(message)); if (err != 0) { logger.error() << "Interface configuration failed:" << strerror(err); @@ -161,16 +174,6 @@ bool WireguardUtilsLinux::updatePeer(const InterfaceConfig& config) { out << "allowed_ip=" << ip.toString() << "\n"; } - out << "jc=" << config.m_junkPacketCount << "\n"; - out << "jmin=" << config.m_junkPacketMinSize << "\n"; - out << "jmax=" << config.m_junkPacketMaxSize << "\n"; - out << "s1=" << config.m_initPacketJunkSize << "\n"; - out << "s2=" << config.m_responsePacketJunkSize << "\n"; - out << "h1=" << config.m_initPacketMagicHeader << "\n"; - out << "h2=" << config.m_responsePacketMagicHeader << "\n"; - out << "h3=" << config.m_underloadPacketMagicHeader << "\n"; - out << "h4=" << config.m_transportPacketMagicHeader << "\n"; - // Exclude the server address, except for multihop exit servers. if ((config.m_hopType != InterfaceConfig::MultiHopExit) && (m_rtmonitor != nullptr)) { diff --git a/client/platforms/macos/daemon/wireguardutilsmacos.cpp b/client/platforms/macos/daemon/wireguardutilsmacos.cpp index 2170d69e..ef13f4c7 100644 --- a/client/platforms/macos/daemon/wireguardutilsmacos.cpp +++ b/client/platforms/macos/daemon/wireguardutilsmacos.cpp @@ -100,6 +100,19 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) { QTextStream out(&message); out << "private_key=" << QString(privateKey.toHex()) << "\n"; out << "replace_peers=true\n"; + + if (config.m_junkPacketCount != "") { + out << "jc=" << config.m_junkPacketCount << "\n"; + out << "jmin=" << config.m_junkPacketMinSize << "\n"; + out << "jmax=" << config.m_junkPacketMaxSize << "\n"; + out << "s1=" << config.m_initPacketJunkSize << "\n"; + out << "s2=" << config.m_responsePacketJunkSize << "\n"; + out << "h1=" << config.m_initPacketMagicHeader << "\n"; + out << "h2=" << config.m_responsePacketMagicHeader << "\n"; + out << "h3=" << config.m_underloadPacketMagicHeader << "\n"; + out << "h4=" << config.m_transportPacketMagicHeader << "\n"; + } + int err = uapiErrno(uapiCommand(message)); if (err != 0) { logger.error() << "Interface configuration failed:" << strerror(err); @@ -163,17 +176,6 @@ bool WireguardUtilsMacos::updatePeer(const InterfaceConfig& config) { out << "allowed_ip=" << ip.toString() << "\n"; } - - out << "jc=" << config.m_junkPacketCount << "\n"; - out << "jmin=" << config.m_junkPacketMinSize << "\n"; - out << "jmax=" << config.m_junkPacketMaxSize << "\n"; - out << "s1=" << config.m_initPacketJunkSize << "\n"; - out << "s2=" << config.m_responsePacketJunkSize << "\n"; - out << "h1=" << config.m_initPacketMagicHeader << "\n"; - out << "h2=" << config.m_responsePacketMagicHeader << "\n"; - out << "h3=" << config.m_underloadPacketMagicHeader << "\n"; - out << "h4=" << config.m_transportPacketMagicHeader << "\n"; - // Exclude the server address, except for multihop exit servers. if ((config.m_hopType != InterfaceConfig::MultiHopExit) && (m_rtmonitor != nullptr)) { diff --git a/client/platforms/windows/daemon/wireguardutilswindows.cpp b/client/platforms/windows/daemon/wireguardutilswindows.cpp index 21df2611..1e0a4752 100644 --- a/client/platforms/windows/daemon/wireguardutilswindows.cpp +++ b/client/platforms/windows/daemon/wireguardutilswindows.cpp @@ -165,16 +165,6 @@ bool WireguardUtilsWindows::updatePeer(const InterfaceConfig& config) { out << "allowed_ip=" << ip.toString() << "\n"; } - out << "jc=" << config.m_junkPacketCount << "\n"; - out << "jmin=" << config.m_junkPacketMinSize << "\n"; - out << "jmax=" << config.m_junkPacketMaxSize << "\n"; - out << "s1=" << config.m_initPacketJunkSize << "\n"; - out << "s2=" << config.m_responsePacketJunkSize << "\n"; - out << "h1=" << config.m_initPacketMagicHeader << "\n"; - out << "h2=" << config.m_responsePacketMagicHeader << "\n"; - out << "h3=" << config.m_underloadPacketMagicHeader << "\n"; - out << "h4=" << config.m_transportPacketMagicHeader << "\n"; - // Exclude the server address, except for multihop exit servers. if (config.m_hopType != InterfaceConfig::MultiHopExit) { m_routeMonitor.addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn)); From 058f8b544e5c16dd6128bc54d3d9c57d645e4785 Mon Sep 17 00:00:00 2001 From: Matthew Schwiebert Date: Thu, 28 Sep 2023 10:14:01 -0400 Subject: [PATCH 147/278] Limit Drawer positioning by dragHeight and total page height --- client/ui/qml/Controls2/DrawerType.qml | 5 ++++- client/ui/qml/Pages2/PageHome.qml | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/client/ui/qml/Controls2/DrawerType.qml b/client/ui/qml/Controls2/DrawerType.qml index c22d00c2..bb2ee4aa 100644 --- a/client/ui/qml/Controls2/DrawerType.qml +++ b/client/ui/qml/Controls2/DrawerType.qml @@ -5,6 +5,7 @@ Drawer { id: drawer property bool needCloseButton: true property bool isOpened: false + property int pageHeight Connections { target: PageController @@ -54,6 +55,7 @@ Drawer { if (needCloseButton) { PageController.drawerOpen() } + position = (dragMargin / pageHeight) } onAboutToHide: { @@ -77,9 +79,10 @@ Drawer { onPositionChanged: { - if (isOpened && (position <= 0.99 && position >= 0.95)) { + if (position < (dragMargin / root.height)) { mouseArea.canceled() drawer.close() + position = 0 mouseArea.exited() dropArea.exited() } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index d8796524..bfebbb21 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -148,6 +148,7 @@ PageType { width: parent.width height: parent.height * 0.9 + pageHeight: root.height ColumnLayout { id: serversMenuHeader From 68095700a2eb6b79cce6677df7a9d07085e3c02b Mon Sep 17 00:00:00 2001 From: ronoaer Date: Thu, 28 Sep 2023 23:21:13 +0800 Subject: [PATCH 148/278] added i18n for v4 --- client/CMakeLists.txt | 22 +- client/amnezia_application.cpp | 20 +- client/main.cpp | 2 +- client/translations/amneziavpn_ru.ts | 2969 ++++++++--------------- client/translations/amneziavpn_zh_CN.ts | 2666 ++++++++++++++++++++ client/ui/models/languageModel.cpp | 19 +- client/ui/models/languageModel.h | 5 +- 7 files changed, 3729 insertions(+), 1974 deletions(-) create mode 100644 client/translations/amneziavpn_zh_CN.ts diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 63810ed3..f149e204 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -50,10 +50,30 @@ endif() qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc) -qt6_add_translations(${PROJECT} TS_FILES +set(AMNEZIAVPN_TR_FILES ${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ru.ts + ${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_zh_CN.ts ) +file(GLOB_RECURSE AMNEZIAVPN_TR_SOURCES *.qrc *.cpp *.h *.ui) +if ( BUILD_TRANSLATIONS ) + qt_create_translation(AMNEZIAVPN_MESSAGES ${AMNEZIAVPN_TR_SOURCES} ${AMNEZIAVPN_TR_FILES}) + qt_add_translation(AMNEZIAVPN_QM_FILES ${AMNEZIAVPN_TR_FILES}) + add_custom_target(amnezia_messages DEPENDS ${AMNEZIAVPN_MESSAGES}) + add_custom_target(amnezia_translations DEPENDS ${AMNEZIAVPN_QM_FILES} amnezia_messages) + add_dependencies(${PROJECT} amnezia_translations) + + if (BUILD_TRANSLATIONS_AS_RESOURCES) + set(QM_FILE_LIST "") + foreach(FILE ${AMNEZIAVPN_QM_FILES}) + list(APPEND QM_FILE_LIST "${FILE}") + endforeach() + string(REPLACE ";" "" QM_FILE_LIST ${QM_FILE_LIST}) + configure_file(${CMAKE_CURRENT_LIST_DIR}/translations/translations.qrc ${CMAKE_CURRENT_LIST_DIR}/translations.qrc) + target_sources(${PROJECT} PRIVATE ${CMAKE_CURRENT_LIST_DIR}/translations.qrc) + endif() +endif( BUILD_TRANSLATIONS ) + if(IOS) #execute_process(COMMAND bash ${CMAKE_CURRENT_LIST_DIR}/scripts/run-build-cloak.sh) execute_process(COMMAND bash ${CMAKE_CURRENT_LIST_DIR}/ios/scripts/openvpn.sh args diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 23157468..d87f326e 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -221,27 +221,21 @@ void AmneziaApplication::loadTranslator() { auto locale = m_settings->getAppLanguage(); m_translator.reset(new QTranslator()); - if (locale != QLocale::English) { - if (m_translator->load(locale, QString("amneziavpn"), QLatin1String("_"), QLatin1String(":/i18n"))) { - if (QCoreApplication::installTranslator(m_translator.get())) { - m_settings->setAppLanguage(locale); - } - } - } + updateTranslator(locale); } + void AmneziaApplication::updateTranslator(const QLocale &locale) { QResource::registerResource(":/translations.qrc"); - if (!m_translator->isEmpty()) + if (!m_translator->isEmpty()) { QCoreApplication::removeTranslator(m_translator.get()); - - if (locale == QLocale::English) { - m_settings->setAppLanguage(locale); - m_engine->retranslate(); } - if (m_translator->load(locale, QString("amneziavpn"), QLatin1String("_"), QLatin1String(":/i18n"))) { + m_settings->setAppLanguage(locale); + + QString strFileName = QString("amneziavpn")+QLatin1String("_")+locale.name()+".qm"; + if (m_translator->load(strFileName, "../../../")) { if (QCoreApplication::installTranslator(m_translator.get())) { m_settings->setAppLanguage(locale); } diff --git a/client/main.cpp b/client/main.cpp index e78a74ff..53c3238f 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -53,13 +53,13 @@ int main(int argc, char *argv[]) app.setOrganizationName(ORGANIZATION_NAME); app.setApplicationDisplayName(APPLICATION_NAME); - app.loadTranslator(); app.loadFonts(); bool doExec = app.parseCommands(); if (doExec) { app.init(); + app.loadTranslator(); qInfo().noquote() << QString("Started %1 version %2").arg(APPLICATION_NAME, APP_VERSION); qInfo().noquote() << QString("%1 (%2)").arg(QSysInfo::prettyProductName(), QSysInfo::currentCpuArchitecture()); diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index f338216b..e6c60a33 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -2,138 +2,65 @@ - AdvancedServerSettingsLogic + AmneziaApplication - - - Clear server from Amnezia software - - - - - Service: - - - - - Uninstalling Amnezia software... - - - - - Error occurred while cleaning the server. - - - - - - Error message: - - - - - - See logs for details. - - - - - Amnezia server successfully uninstalled - - - - - Error occurred while scanning the server. - - - - - All containers installed on the server are added to the GUI - - - - - No installed containers found on the server + + Split tunneling for WireGuard is not implemented, the option was disabled - AppSettingsLogic + AndroidController - - Software version + + AmneziaVPN - - Save log - - - - - Open backup - - - - - Can't import config, file is corrupted. - - - - - ClientInfoLogic - - - Service: - - - - - ClientManagementLogic - - - Service: - - - - - An error occurred while getting the list of clients. + + VPN Connected + Refers to the app - which is currently running the background and waiting ConnectionController - + VPN Protocols is not installed. Please install VPN container at first - + Connection... - - Disconnect + + Connected - + + Settings updated successfully, Reconnnection... + + + + Reconnection... - - - - + + + + Connect - + Disconnection... @@ -157,24 +84,24 @@ - ContextMenu + ContextMenuType - + C&ut - + &Copy - + &Paste - + &SelectAll @@ -182,70 +109,106 @@ ExportController - + Access error! + + + HomeContainersListView - - Save AmneziaVPN config + + The selected protocol is not supported on the current platform + + + + + Reconnect via VPN Procotol: ImportController - - Open config file + + Scanned %1 of %2. InstallController - - + + installed successfully. - - + + is already installed on the server. - - + + Already installed containers were found on the server. All installed containers have been added to the application - + + Settings updated successfully + + + + Server ' - + ' was removed - + All containers from server ' - + has been removed from the server ' - + Please login as the user + + + Server added successfully + + + + + KeyChainClass + + + Read key failed: %1 + + + + + Write key failed: %1 + + + + + Delete key failed: %1 + + NotificationHandler @@ -276,742 +239,57 @@ Already installed containers were found on the server. All installed containers - - PageAbout - - - About Amnezia - - - - - AmneziaVPN is opensource software, it's free forever. Our goal is to make the best VPN client in the world. -<ul> -<li>Sources on <a href="https://github.com/amnezia-vpn/desktop-client">GitHub</a></li> -<li><a href="https://amnezia.org/">Web Site</a></li> -<li><a href="https://t.me/amnezia_vpn_en">Telegram group</a></li> -<li><a href="https://signal.group/#CjQKIB2gUf8QH_IXnOJMGQWMDjYz9cNfmRQipGWLFiIgc4MwEhAKBONrSiWHvoUFbbD0xwdh">Signal group</a></li> -</ul> - - - - - - Support - - - - - Have questions? You can get support by: -<ul> -<li><a href="https://t.me/amnezia_vpn_en">Telegram group</a> (preferred way)</li> -<li>Create issue on <a href="https://github.com/amnezia-vpn/desktop-client/issues">GitHub</a></li> -<li>Email to: <a href="support@amnezia.org">support@amnezia.org</a></li> -</ul> - - - - - Donate - - - - - Please support Amnezia project by donation, we really need it now more than ever. -<ul> -<li>By credit card on <a href="https://www.patreon.com/amneziavpn">Patreon</a> (starting from $1)</li> -<li>Send some coins to addresses listed <a href="https://github.com/amnezia-vpn/desktop-client/blob/master/README.md">on GitHub page</a></li> -</ul> - - - - - - PageAdvancedServerSettings - - - Advanced server settings - - - - - Clients Management - - - - - PageAppSetting - - - Application Settings - - - - - Auto connect - - - - - Auto start - - - - - Start minimized - - - - - Check for updates - - - - - Keep logs - - - - - Open logs folder - - - - - Export logs - - - - - Clear logs - - - - - Cleared - - - - - Backup and restore configuration - - - - - Backup app config - - - - - Restore app config - - - - - PageClientInfoOpenVPN - - - Client Info - - - - - Client name - - - - - Certificate id - - - - - Certificate - - - - - Revoke Certificate - - - - - PageClientInfoWireGuard - - - Client Info - - - - - Client name - - - - - Public Key - - - - - Revoke Key - - - - - PageClientManagement - - - Clients Management - - - PageDeinstalling - + Removing services from - + Usually it takes no more than 5 minutes - - PageGeneralSettings - - - App settings - - - - - Network settings - - - - - Server Settings - - - - - Share connection - - - - - Servers - - - - - Add server - - - - - Exit - - - PageHome - + VPN protocol - + Servers - - PageNetworkSetting - - - DNS Servers - - - - - Use AmneziaDNS service (recommended) - - - - - Use AmneziaDNS container on your server, when it installed. - -Your AmneziaDNS server available only when it installed and VPN connected, it has internal IP address 172.29.172.254 - -If AmneziaDNS service is not installed on the same server, or this option is unchecked, the following DNS servers will be used: - - - - - Primary DNS server - - - - - - Reset to default - - - - - Secondary DNS server - - - - - PageNewServer - - - Setup your server to use VPN - - - - - If you want easily configure your server just run Wizard - - - - - Run Setup Wizard - - - - - Press configure manually to choose VPN protocols you want to install - - - - - Configure - - - - - PageNewServerProtocols - - - Select VPN protocols - - - - - Setup server - - - - - Select protocol container - - - - - Port - - - - - Network Protocol - - - - - udp - - - - - tcp - - - - - PageProtoCloak - - - Cloak Settings - - - - - Cipher - - - - - chacha20-poly1305 - - - - - aes-256-gcm - - - - - aes-192-gcm - - - - - aes-128-gcm - - - - - Fake Web Site - - - - - Port - - - - - Save and restart VPN - - - - - Cancel - - - - - PageProtoOpenVPN - - - OpenVPN Settings - - - - - VPN Addresses Subnet - - - - - Network protocol - - - - - TCP - - - - - UDP - - - - - Port - - - - - Auto-negotiate encryption - - - - - Cipher - - - - - AES-256-GCM - - - - - AES-192-GCM - - - - - AES-128-GCM - - - - - AES-256-CBC - - - - - AES-192-CBC - - - - - AES-128-CBC - - - - - ChaCha20-Poly1305 - - - - - ARIA-256-CBC - - - - - CAMELLIA-256-CBC - - - - - none - - - - - Hash - - - - - SHA512 - - - - - SHA384 - - - - - SHA256 - - - - - SHA3-512 - - - - - SHA3-384 - - - - - SHA3-256 - - - - - whirlpool - - - - - BLAKE2b512 - - - - - BLAKE2s256 - - - - - SHA1 - - - - - Enable TLS auth - - - - - Block DNS requests outside of VPN - - - - - Additional client config commands → - - - - - Additional server config commands → - - - - - Save and restart VPN - - - - - Cancel - - - - - PageProtoSftp - - - SFTP settings - - - - - Port - - - - - User Name - - - - - Password - - - - - Restore drive when client starts - - - - - Mount drive - - - - - PageProtoShadowSocks - - - ShadowSocks Settings - - - - - Cipher - - - - - chacha20-ietf-poly1305 - - - - - xchacha20-ietf-poly1305 - - - - - aes-256-gcm - - - - - aes-192-gcm - - - - - aes-128-gcm - - - - - Port - - - - - Save and restart VPN - - - - - Cancel - - - - - PageProtoTorWebSite - - - Tor Web Site settings - - - - - Web site onion address - - - - - Notes:<ul> -<li>Use <a href="https://www.torproject.org/download/">Tor Browser</a> to open this url.</li> -<li>After installation it takes several minutes while your onion site will become available in the Tor Network.</li> -<li>When configuring WordPress set the domain as this onion address.</li> -</ul> - - - - - - PageProtoWireGuard - - - WireGuard Settings - - - PageProtocolCloakSettings - - Settings updated successfully - - - - + Cloak settings - + Disguised as traffic from - + Port + - Cipher - + Save and Restart Amnezia @@ -1019,195 +297,195 @@ If AmneziaDNS service is not installed on the same server, or this option is unc PageProtocolOpenVpnSettings - - Settings updated successfully - - - - + OpenVPN settings - + VPN Addresses Subnet - + Network protocol - + Port - + Auto-negotiate encryption - - + + Hash - + SHA512 - + SHA384 - + SHA256 - + SHA3-512 - + SHA3-384 - + SHA3-256 - + whirlpool - + BLAKE2b512 - + BLAKE2s256 - + SHA1 - - + + Cipher - + AES-256-GCM - + AES-192-GCM - + AES-128-GCM - + AES-256-CBC - + AES-192-CBC - + AES-128-CBC - + ChaCha20-Poly1305 - + ARIA-256-CBC - + CAMELLIA-256-CBC - + none - + TLS auth - + Block DNS requests outside of VPN - + Additional client configuration commands - - + + Commands: - + Additional server configuration commands - + Remove OpenVPN - + Remove OpenVpn from server? - + + All users with whom you shared a connection will no longer be able to connect to it + + + + Continue Продолжить - + Cancel - + Save and Restart Amnezia @@ -1242,11 +520,16 @@ If AmneziaDNS service is not installed on the same server, or this option is unc + All users with whom you shared a connection will no longer be able to connect to it + + + + Continue Продолжить - + Cancel @@ -1254,177 +537,96 @@ If AmneziaDNS service is not installed on the same server, or this option is unc PageProtocolShadowSocksSettings - - Settings updated successfully - - - - + ShadowSocks settings - + Port - - + + Cipher - + Save and Restart Amnezia - - PageQrDecoderIos - - - Import configuration - - - - - PageServerConfiguringProgress - - - Configuring... - - - - - Please wait. - - - - - Cancel - - - PageServerContainers - - - Install new service + Continue + Продолжить + + + + PageServiceDnsSettings + + + A DNS service is installed on your server, and it is only accessible via VPN. + - - Installed services + + The DNS address is the same as the address of your server. You can configure DNS in the settings, under the connections tab. - - Default + + + Remove - - Port + + from server? - - Network Protocol - - - - - udp - - - - - tcp - - - - - Cancel - - - - + Continue Продолжить - - Installed Protocols and Services - - - - - Remove container - - - - - This action will erase all data of this container on the server. - - - - - PageServerList - - - Servers - - - - - PageServerSettings - - - Server settings - - - - - Protocols and Services - - - - - Share Server (FULL ACCESS) - - - - - Advanced server settings - - - - - Forget this server + + Cancel PageServiceSftpSettings - + Settings updated successfully - + SFTP settings - + Host - + + + + + Copied + + + + Port @@ -1434,54 +636,54 @@ If AmneziaDNS service is not installed on the same server, or this option is unc - + Password - + Mount folder on device - + In order to mount remote SFTP folder as local drive, perform following steps: <br> - - + + <br>1. Install the latest version of - - + + <br>2. Install the latest version of - + Detailed instructions - + Remove SFTP and all data stored there - - Some description + + Remove SFTP and all data stored there? - + Continue Продолжить - + Cancel @@ -1489,52 +691,57 @@ If AmneziaDNS service is not installed on the same server, or this option is unc PageServiceTorWebsiteSettings - + Settings updated successfully - + Tor website settings - + Website address - + + Copied + + + + Use <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor Browser</a> to open this url. - + After installation it takes several minutes while your onion site will become available in the Tor Network. - + When configuring WordPress set the domain as this onion address. - + Remove website - - Some description + + The site with all data will be removed from the tor network. - + Continue Продолжить - + Cancel @@ -1542,32 +749,32 @@ If AmneziaDNS service is not installed on the same server, or this option is unc PageSettings - + Settings - + Servers - + Connection - + Application - + Backup - + About AmneziaVPN @@ -1591,47 +798,67 @@ And if you don't like the app, all the more support it - the donation will - + + https://www.patreon.com/amneziavpn + + + + Show other methods on Github - + Contacts - + Telegram group - + To discuss features - - Mail + + https://t.me/amnezia_vpn_en + Mail + + + + For reviews and bug reports - + Github - + + https://github.com/amnezia-vpn/amnezia-client + + + + Website - + + https://amnezia.org + + + + Check for updates @@ -1644,47 +871,72 @@ And if you don't like the app, all the more support it - the donation will - + + Auto start + + + + + Launch the application every time + + + + + starts + + + + + Start minimized + + + + + Launch application minimized + + + + Language - + Logging - + Enabled - + Disabled - + Reset settings and remove all data from the application - + Reset settings and remove all data from the application? - + All settings will be reset to default. All installed AmneziaVPN services will still remain on the server. - + Continue Продолжить - + Cancel @@ -1692,35 +944,71 @@ And if you don't like the app, all the more support it - the donation will PageSettingsBackup - + Backup - + Settings restored from backup file - + Configuration backup - + It will help you instantly restore connection settings at the next installation - + Make a backup - + + Save backup file + + + + + + Backup files (*.backup) + + + + Restore from backup + + + Open backup file + + + + + Import settings from a backup file? + + + + + All current settings will be reset + + + + + Continue + Продолжить + + + + Cancel + + PageSettingsConnection @@ -1729,43 +1017,48 @@ And if you don't like the app, all the more support it - the donation will Connection + + + Auto connect + + + Connect to VPN on app start + + + + Use AmneziaDNS if installed on the server - - Internal IP address 172.29.172.254 - - - - + DNS servers - + If AmneziaDNS is not used or installed - + Split site tunneling - + Allows you to connect to some sites through a secure connection, and to others bypassing it - + Separate application tunneling - + Allows you to use the VPN only for certain applications @@ -1773,45 +1066,101 @@ And if you don't like the app, all the more support it - the donation will PageSettingsDns - + DNS servers - + If AmneziaDNS is not used or installed - + + Restore default + + + + + Restore default DNS settings? + + + + + Continue + Продолжить + + + + Cancel + + + + + Settings have been reset + + + + Save + + + Settings saved + + PageSettingsLogging - + Logging - + + Save logs - + Open folder with logs - + + Logs files (*.log) + + + + Save logs to file - + + Clear logs? + + + + + Continue + Продолжить + + + + Cancel + + + + + Logs have been cleaned up + + + + Clear logs @@ -1824,109 +1173,109 @@ And if you don't like the app, all the more support it - the donation will - - No installed containers found - - - - + Clear Amnezia cache - + May be needed when changing other settings - + Clear cached profiles? Очистить закешированные профили - - some description + + No new installed containers found - - - + + + + + + + + Continue Продолжить - - - + + + Cancel - + Check the server for previously installed Amnezia services - + Add them to the application if they were not displayed - + Remove server from application - + Remove server? - + All installed AmneziaVPN services will still remain on the server. - + Clear server from Amnezia software - + Clear server from Amnezia software? - - All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted. + + All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted. PageSettingsServerInfo - + Server name - + Save - + Protocols - + Services - + Data @@ -1939,23 +1288,28 @@ And if you don't like the app, all the more support it - the donation will - - + + Remove - + from server? - + + All users with whom you shared a connection will no longer be able to connect to it + + + + Continue Продолжить - + Cancel @@ -1969,85 +1323,137 @@ And if you don't like the app, all the more support it - the donation will - PageSetupWizard + PageSettingsSplitTunneling - - Setup your server to use VPN + + Only the addresses in the list must be opened via VPN - - High censorship level + + Addresses from the list should never be opened via VPN - - I'm living in a country with a high censorship level. Many of the foreign websites and VPNs are blocked by my government. I want to setup a reliable VPN, which can not be detected by my internet provider and my government. -OpenVPN and ShadowSocks over Cloak (VPN obfuscation) profiles will be installed. - + + Split site tunneling - - Medium censorship level + + Mode - - I'm living in a country with a medium censorship level. Some websites are blocked by my government, but VPNs are not blocked at all. I want to setup a flexible solution. -OpenVPN over ShadowSocks profile will be installed. - + + Remove - - Low censorship level + + Continue + Продолжить + + + + Cancel - - I want to improve my privacy on the internet. -OpenVPN profile will be installed. - + + Site or IP - - Next + + Import/Export Sites + + + + + Import + + + + + Save site list + + + + + Save sites + + + + + + + Sites files (*.json) + + + + + Import a list of sites + + + + + Replace site list + + + + + + Open sites file + + + + + Add imported sites to existing ones PageSetupWizardConfigSource - + Server connection - + Do not use connection code from public sources. It may have been created to intercept your data. -It's okay if a friend passed the code. +It's okay as long as it's from someone you trust. - + What do you have? - + File with connection settings - + + File with connection settings or backup + + + + + Open config file + + + + QR-code - + Key as text @@ -2070,43 +1476,37 @@ It's okay if a friend passed the code. - - - Insert - - - - + Password / SSH private key - + Continue Продолжить - + Enter the address in the format 255.255.255.255:88 - + Login to connect via SSH - + Ip address cannot be empty - + Login cannot be empty - + Password/private key cannot be empty @@ -2119,123 +1519,60 @@ It's okay if a friend passed the code. - + Set up a VPN yourself - + I want to choose a VPN protocol - + Continue Продолжить - - - PageSetupWizardHighLevel - - Setup Wizard - - - - - AmneziaVPN will install a VPN protocol which is not visible to your internet provider and government firewall. Your VPN connection will be seen by your internet provider as regular web traffic to a particular website. - -You SHOULD set this website address to some foreign website which is not blocked by your internet provider. In other words, you need to type some foreign website address which is accessible to you without a VPN. - - - - - Type another web site address for masking or keep it by default. Your internet provider will think you working on this web site when you connected to VPN. - - - - - OpenVPN and ShadowSocks over Cloak (VPN obfuscation) profiles will be installed. - -This protocol support exporting connection profiles to mobile devices by exporting ShadowSocks and Cloak configs (you should launch the 3rd party open source VPN client - ShadowSocks VPN and install Cloak plugin). - - - - - Next + + Set up later PageSetupWizardInstalling - + The server has already been added to the application - + + Amnesia has detected that your server is currently + + + + + busy installing other software. Amnesia installation + + + + + will pause until the server finishes installing other software + + + + Installing - + + Usually it takes no more than 5 minutes - - PageSetupWizardLowLevel - - - Setup Wizard - - - - - AmneziaVPN will install the OpenVPN protocol with public/private key pairs generated on both server and client sides. - -You can also configure the connection on your mobile device by copying the exported ".ovpn" file to your device, and setting up the official OpenVPN client. - -We recommend not to use messaging applications for sending the connection profile - it contains VPN private keys. - - - - - OpenVPN profile will be installed - - - - - Start configuring - - - - - PageSetupWizardMediumLevel - - - Setup Wizard - - - - - AmneziaVPN will install a VPN protocol which is difficult to detect by your internet provider and government firewall (but possible). In most cases, this is the most suitable protocol. This protocol is faster compared to the VPN protocols with "VPN masking". - -This protocol supports exporting connection profiles to mobile devices by using QR codes (you should launch the 3rd party open source VPN client - ShadowSocks VPN). - - - - - OpenVPN over ShadowSocks profile will be installed - - - - - Next - - - PageSetupWizardProtocolSettings @@ -2243,38 +1580,28 @@ This protocol supports exporting connection profiles to mobile devices by using Installing - - - protocol description - - More detailed - - detailed protocol description - - - - + Close - + Network protocol - + Port - + Install @@ -2295,30 +1622,35 @@ This protocol supports exporting connection profiles to mobile devices by using PageSetupWizardQrReader - - Point the camera at the QR code and hold for a couple of seconds. + + Point the camera at the QR code and hold for a couple of seconds. PageSetupWizardStart - + + Settings restored from backup file + + + + Free service for creating a personal VPN on your server. - + Helps you access blocked content without revealing your privacy, even to VPN providers. - + I have the data to connect - + I have nothing @@ -2351,33 +1683,6 @@ This protocol supports exporting connection profiles to mobile devices by using Продолжить - - PageSetupWizardVPNMode - - - Setup Wizard - - - - - Optional. - -You can enable VPN mode "For selected sites" and add blocked sites you need to visit manually. If you will choose this option, you will need add every blocked site you want to visit to the access list. You may switch between modes later. - -Please note, you should add addresses to the list after VPN connection established. You may add any domain, URL or IP address, it will be resolved to IP address. - - - - - Turn on mode "VPN for selected sites" - - - - - Start configuring - - - PageSetupWizardViewConfig @@ -2386,22 +1691,22 @@ Please note, you should add addresses to the list after VPN connection establish - + Do not use connection code from public sources. It could be created to intercept your data. - + Collapse content - + Show content - + Connect @@ -2409,76 +1714,91 @@ Please note, you should add addresses to the list after VPN connection establish PageShare - + OpenVpn native format - + WireGuard native format - + VPN Access - + Connection - + VPN access without the ability to manage the server - + Full access to server - - Server and service - - - - + Server - + Accessing - - Protocols and services - - - - + Connection to - - + + File with connection settings to - + + Save OpenVPN config + + + + + Save WireGuard config + + + + For the AmneziaVPN app - + Full access + + + Servers + + + + + Protocols + + + + + Protocol + + @@ -2486,454 +1806,11 @@ Please note, you should add addresses to the list after VPN connection establish - + Share - - PageShareConnection - - - Share protocol config - - - - - Share for Amnezia - - - - - Share for - - - - - PageShareProtoAmnezia - - - Share for Amnezia - - - - - Anyone who logs in with this code will have the same permissions to use VPN and YOUR SERVER as you. - -This code includes your server credentials! - -Provide this code only to TRUSTED users. - - - - - Anyone who logs in with this code will be able to connect to this VPN server. - -This code does not include server credentials. - -New encryption keys pair will be generated. - - - - - Share - - - - - Save to file - - - - - Save AmneziaVPN config - - - - - Scan QR code using AmneziaVPN mobile - - - - - PageShareProtoCloak - - - Share Cloak Settings - - - - - Note: Cloak protocol using same password for all connections - - - - - Share - - - - - Save to file - - - - - Save AmneziaVPN config - - - - - PageShareProtoIkev2 - - - Share IKEv2 Settings - - - - - - Export p12 certificate - - - - - - Export config for Apple - - - - - - Export config for StrongSwan - - - - - PageShareProtoOpenVPN - - - Share OpenVPN Settings - - - - - New encryption keys pair will be generated. - - - - - Share - - - - - Save to file - - - - - Save OpenVPN config - - - - - PageShareProtoSftp - - - Share SFTP settings - - - - - PageShareProtoShadowSocks - - - Share ShadowSocks Settings - - - - - Note: ShadowSocks protocol using same password for all connections - - - - - Copy config - - - - - Connection string - - - - - Copy string - - - - - PageShareProtoTorWebSite - - - Share Tor Web site - - - - - PageShareProtoWireGuard - - - Share WireGuard Settings - - - - - New encryption keys pair will be generated. - - - - - Share - - - - - Save to file - - - - - Save OpenVPN config - - - - - PageShareProtocolBase - - - Generate config - - - - - Generating config... - - - - - Show config - - - - - PageSites - - - Web site/Hostname/IP address/Subnet - - - - - yousite.com or IP address - - - - - Import IP addresses - - - - - Delete selected - - - - - Select all - - - - - Export all - - - - - PageStart - - - Setup your server to use VPN - - - - - Connect to the already created VPN server - - - - - - Set up your own server - - - - - Import connection - - - - - Connection code - - - - - Connect - - - - - Open file - - - - - Scan QR code - - - - - Restore app config - - - - - How to get own server? → - - - - - Server IP address [:port] - - - - - Login to connect via SSH - - - - - - Password - - - - - - Connect using SSH key - - - - - Private key - - - - - Connect using SSH password - - - - - PageVPN - - - Donate - - - - - Server - - - - - Profile - - - - - Proto - - - - - DNS - - - - - How to use VPN - - - - - For all connections - - - - - Except selected sites - - - - - For selected sites - - - - - + Add site - - - - - PageViewConfig - - - Check config - - - - - Attention! -The config above contains cached OpenVPN connection profile. -AmneziaVPN detected this profile may contain malicious scripts. Please, carefully review the config and import this config only if you completely trust it. - - - - - Suspicious string: - - - - - Cached connection profile: - - - - - Cancel - - - - - Import config - - - PopupType @@ -2942,6 +1819,241 @@ AmneziaVPN detected this profile may contain malicious scripts. Please, carefull + + QKeychain::DeletePasswordJobPrivate + + + Password entry not found + + + + + Could not decrypt data + + + + + + Unknown error + + + + + Could not open wallet: %1; %2 + + + + + Password not found + + + + + Could not open keystore + + + + + Could not remove private key from keystore + + + + + QKeychain::JobPrivate + + + Unknown error + + + + + Access to keychain denied + + + + + QKeychain::PlainTextStore + + + Could not store data in settings: access error + + + + + Could not store data in settings: format error + + + + + Could not delete data from settings: access error + + + + + Could not delete data from settings: format error + + + + + Entry not found + + + + + QKeychain::ReadPasswordJobPrivate + + + Password entry not found + + + + + + Could not decrypt data + + + + + D-Bus is not running + + + + + + Unknown error + + + + + No keychain service available + + + + + Could not open wallet: %1; %2 + + + + + Access to keychain denied + + + + + Could not determine data type: %1; %2 + + + + + + Entry not found + + + + + Unsupported entry type 'Map' + + + + + Unknown kwallet entry type '%1' + + + + + Password not found + + + + + Could not open keystore + + + + + Could not retrieve private key from keystore + + + + + Could not create decryption cipher + + + + + QKeychain::WritePasswordJobPrivate + + + Credential size exceeds maximum size of %1 + + + + + Credential key exceeds maximum size of %1 + + + + + Writing credentials failed: Win32 error code %1 + + + + + Encryption failed + + + + + D-Bus is not running + + + + + + Unknown error + + + + + Could not open wallet: %1; %2 + + + + + Password not found + + + + + Could not open keystore + + + + + Could not create private key generator + + + + + Could not generate new private key + + + + + Could not retrieve private key from keystore + + + + + Could not create encryption cipher + + + + + Could not encrypt data + + + QObject @@ -3155,13 +2267,7 @@ AmneziaVPN detected this profile may contain malicious scripts. Please, carefull - - - Web site in Tor network - - - - + DNS Service @@ -3170,62 +2276,150 @@ AmneziaVPN detected this profile may contain malicious scripts. Please, carefull Sftp file sharing service + + + + Website in Tor network + + Amnezia DNS - - - OpenVPN container - - - Container with OpenVpn and ShadowSocks + OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its own security protocol with SSL/TLS for key exchange. - - Container with OpenVpn and ShadowSocks protocols configured with traffic masking by Cloak plugin + + ShadowSocks - masks VPN traffic, making it similar to normal web traffic, but is recognised by analysis systems in some highly censored regions. - - WireGuard container - - - - - IPsec container + + OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against active-probbing detection. Ideal for bypassing blocking in regions with the highest levels of censorship. + WireGuard - New popular VPN protocol with high performance, high speed and low power consumption. Recommended for regions with low levels of censorship. + + + + + IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS. + + + + + Deploy a WordPress site on the Tor network in two clicks. + + + + + Replace the current DNS server with your own. This will increase your privacy level. + + + + + Creates a file vault on your server to securely store and transfer files. + + + + + OpenVPN container + + + + + Container with OpenVpn and ShadowSocks + + + + + Container with OpenVpn and ShadowSocks protocols configured with traffic masking by Cloak plugin + + + + + WireGuard container + + + + + IPsec container + + + + Sftp file sharing service - is secure FTP service - + Sftp service - - An error occurred while saving the list of clients. - - - - - SelectContainer - - - VPN containers + + Entry not found - - Other containers + + Access to keychain denied + + + + + No keyring daemon + + + + + Already unlocked + + + + + No such keyring + + + + + Bad arguments + + + + + I/O error + + + + + Cancelled + + + + + Keyring already exists + + + + + No match + + + + + Unknown error + + + + + error 0x%1: %2 @@ -3237,62 +2431,6 @@ AmneziaVPN detected this profile may contain malicious scripts. Please, carefull - - ServerConfiguringProgressLogic - - - - Please wait, configuring process may take up to 5 minutes - - - - - Configuring... - - - - - Operation finished - - - - - ServerContainersLogic - - - Error occurred while configuring server. - - - - - Error message: - - - - - See logs for details. - - - - - ServerSettingsLogic - - - - Clear client cached profile - - - - - Service: - - - - - Cache cleared - - - Settings @@ -3310,130 +2448,100 @@ AmneziaVPN detected this profile may contain malicious scripts. Please, carefull SettingsController - + Software version - - Save log + + All settings have been reset to default values - - Backup application config + + Cached profiles cleared - - Open backup - - - - - Backup file is empty - - - - + Backup file is corrupted - - ShareConnectionButtonCopyType - - - Copy - - - - - Copied - - - ShareConnectionDrawer - + + + Save AmneziaVPN config + + + + Share - + Copy - + + Copied + + + + Show content - - To read the QR code in the Amnezia app, select "Add Server" → "I have connection details" + + To read the QR code in the Amnezia app, select "Add server" → "I have data to connect" → "QR code, key or settings file" - ShareConnectionLogic + SitesController - - Error while generating connection profile + + Hostname not look like ip adress or domain name - - Error occurred while generating the config. + + New site added: - - Error message: + + Site removed: - - See logs for details. - - - - - SitesLogic - - - These sites will be opened using VPN + + Can't open file: - - These sites will be excepted from VPN - - - - - StartPageLogic - - - - Connect + + Failed to parse JSON data from file: - - - Please fill in all fields + + The JSON data is not an array in file: - - Connecting... + + Import completed - - Open config file + + Export completed @@ -3465,57 +2573,14 @@ AmneziaVPN detected this profile may contain malicious scripts. Please, carefull - - UiLogic - - - Error occurred while configuring server. - - - - - Error message: - - - - - See logs for details. - - - VpnConnection - + Mbps - - VpnLogic - - - - 0 Mbps - - - - - AmneziaVPN not supporting selected protocol on this device. Select another protocol. - - - - - VPN Protocols is not installed. - Please install VPN container at first - - - - - VPN Protocol not chosen - - - VpnProtocol @@ -3562,56 +2627,46 @@ AmneziaVPN detected this profile may contain malicious scripts. Please, carefull amnezia::ContainerProps - + Low - + High - + Medium - + Many foreign websites and VPN providers are blocked - + Some foreign sites are blocked, but VPN providers are not blocked - + I just want to increase the level of privacy - main + main2 - - It's public key. Private key required + + Private key passphrase - - Ssh log - - - - - App log - - - - - Wrap words + + Save diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts new file mode 100644 index 00000000..257a3378 --- /dev/null +++ b/client/translations/amneziavpn_zh_CN.ts @@ -0,0 +1,2666 @@ + + + + + AmneziaApplication + + + Split tunneling for WireGuard is not implemented, the option was disabled + + + + + AndroidController + + + AmneziaVPN + + + + + VPN Connected + Refers to the app - which is currently running the background and waiting + + + + + ConnectionController + + + + + + Connect + 连接 + + + + VPN Protocols is not installed. + Please install VPN container at first + 未安装VPN协议,请安装 + + + + Connection... + 连接中 + + + + Connected + 已连接 + + + + Reconnection... + 重连中 + + + + Disconnection... + 断开中 + + + + Settings updated successfully, Reconnnection... + 配置已更新,重连中 + + + + ConnectionTypeSelectionDrawer + + + Connection data + + + + + Server IP, login and password + + + + + QR code, key or configuration file + + + + + ContextMenuType + + + C&ut + + + + + &Copy + + + + + &Paste + + + + + &SelectAll + + + + + ExportController + + + Access error! + + + + + HomeContainersListView + + + The selected protocol is not supported on the current platform + + + + + Reconnect via VPN Procotol: + + + + + ImportController + + + Scanned %1 of %2. + + + + + InstallController + + + + installed successfully. + + + + + + is already installed on the server. + + + + + + +Already installed containers were found on the server. All installed containers have been added to the application + + + + + Settings updated successfully + + + + + Server ' + + + + + ' was removed + + + + + All containers from server ' + + + + + has been removed from the server ' + + + + + Please login as the user + + + + + Server added successfully + + + + + KeyChainClass + + + Read key failed: %1 + + + + + Write key failed: %1 + + + + + Delete key failed: %1 + + + + + NotificationHandler + + + + AmneziaVPN + + + + + VPN Connected + + + + + VPN Disconnected + + + + + AmneziaVPN notification + + + + + Unsecured network detected: + + + + + PageDeinstalling + + + Removing services from + + + + + Usually it takes no more than 5 minutes + + + + + PageHome + + + VPN protocol + + + + + Servers + + + + + PageProtocolCloakSettings + + + Cloak settings + + + + + Disguised as traffic from + + + + + Port + + + + + + Cipher + + + + + Save and Restart Amnezia + + + + + PageProtocolOpenVpnSettings + + + OpenVPN settings + + + + + VPN Addresses Subnet + + + + + Network protocol + + + + + Port + + + + + Auto-negotiate encryption + + + + + + Hash + + + + + SHA512 + + + + + SHA384 + + + + + SHA256 + + + + + SHA3-512 + + + + + SHA3-384 + + + + + SHA3-256 + + + + + whirlpool + + + + + BLAKE2b512 + + + + + BLAKE2s256 + + + + + SHA1 + + + + + + Cipher + + + + + AES-256-GCM + + + + + AES-192-GCM + + + + + AES-128-GCM + + + + + AES-256-CBC + + + + + AES-192-CBC + + + + + AES-128-CBC + + + + + ChaCha20-Poly1305 + + + + + ARIA-256-CBC + + + + + CAMELLIA-256-CBC + + + + + none + + + + + TLS auth + + + + + Block DNS requests outside of VPN + + + + + Additional client configuration commands + + + + + + Commands: + + + + + Additional server configuration commands + + + + + Remove OpenVPN + + + + + Remove OpenVpn from server? + + + + + All users with whom you shared a connection will no longer be able to connect to it + + + + + Continue + + + + + Cancel + + + + + Save and Restart Amnezia + + + + + PageProtocolRaw + + + settings + + + + + Show connection options + + + + + Connection options + + + + + + Remove + + + + + from server? + + + + + All users with whom you shared a connection will no longer be able to connect to it + + + + + Continue + + + + + Cancel + + + + + PageProtocolShadowSocksSettings + + + ShadowSocks settings + + + + + Port + + + + + + Cipher + + + + + Save and Restart Amnezia + + + + + PageServiceDnsSettings + + + A DNS service is installed on your server, and it is only accessible via VPN. + + + + + + The DNS address is the same as the address of your server. You can configure DNS in the settings, under the connections tab. + + + + + + Remove + + + + + from server? + + + + + Continue + + + + + Cancel + + + + + PageServiceSftpSettings + + + Settings updated successfully + + + + + SFTP settings + + + + + Host + + + + + + + + Copied + + + + + Port + + + + + Login + + + + + Password + + + + + Mount folder on device + + + + + In order to mount remote SFTP folder as local drive, perform following steps: <br> + + + + + + <br>1. Install the latest version of + + + + + + <br>2. Install the latest version of + + + + + Detailed instructions + + + + + Remove SFTP and all data stored there + + + + + Remove SFTP and all data stored there? + + + + + Continue + + + + + Cancel + + + + + PageServiceTorWebsiteSettings + + + Settings updated successfully + + + + + Tor website settings + + + + + Website address + + + + + Copied + + + + + Use <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor Browser</a> to open this url. + + + + + After installation it takes several minutes while your onion site will become available in the Tor Network. + + + + + When configuring WordPress set the domain as this onion address. + + + + + Remove website + + + + + The site with all data will be removed from the tor network. + + + + + Continue + + + + + Cancel + + + + + PageSettings + + + Settings + + + + + Servers + + + + + Connection + + + + + Application + + + + + Backup + + + + + About AmneziaVPN + + + + + PageSettingsAbout + + + Support the project with a donation + + + + + This is a free and open source application. If you like it, support the developers with a donation. +And if you don't like the app, all the more support it - the donation will be used to improve the app. + + + + + Card on Patreon + + + + + https://www.patreon.com/amneziavpn + + + + + Show other methods on Github + + + + + Contacts + + + + + Telegram group + + + + + To discuss features + + + + + https://t.me/amnezia_vpn_en + + + + + Mail + + + + + For reviews and bug reports + + + + + Github + + + + + https://github.com/amnezia-vpn/amnezia-client + + + + + Website + + + + + https://amnezia.org + + + + + Check for updates + + + + + PageSettingsApplication + + + Application + + + + + Auto start + + + + + Launch the application every time + + + + + starts + + + + + Start minimized + + + + + Launch application minimized + + + + + Language + + + + + Logging + + + + + Enabled + + + + + Disabled + + + + + Reset settings and remove all data from the application + + + + + Reset settings and remove all data from the application? + + + + + All settings will be reset to default. All installed AmneziaVPN services will still remain on the server. + + + + + Continue + + + + + Cancel + + + + + PageSettingsBackup + + + Settings restored from backup file + + + + + Backup + + + + + Configuration backup + + + + + It will help you instantly restore connection settings at the next installation + + + + + Make a backup + + + + + Save backup file + + + + + + Backup files (*.backup) + + + + + Restore from backup + + + + + Open backup file + + + + + Import settings from a backup file? + + + + + All current settings will be reset + + + + + Continue + + + + + Cancel + + + + + PageSettingsConnection + + + Connection + + + + + Auto connect + + + + + Connect to VPN on app start + + + + + Use AmneziaDNS if installed on the server + + + + + DNS servers + + + + + If AmneziaDNS is not used or installed + + + + + Split site tunneling + + + + + Allows you to connect to some sites through a secure connection, and to others bypassing it + + + + + Separate application tunneling + + + + + Allows you to use the VPN only for certain applications + + + + + PageSettingsDns + + + DNS servers + + + + + If AmneziaDNS is not used or installed + + + + + Restore default + + + + + Restore default DNS settings? + + + + + Continue + + + + + Cancel + + + + + Settings have been reset + + + + + Save + + + + + Settings saved + + + + + PageSettingsLogging + + + Logging + + + + + + Save logs + + + + + Open folder with logs + + + + + Logs files (*.log) + + + + + Save logs to file + + + + + Clear logs? + + + + + Continue + + + + + Cancel + + + + + Logs have been cleaned up + + + + + Clear logs + + + + + PageSettingsServerData + + + All installed containers have been added to the application + + + + + No new installed containers found + + + + + Clear Amnezia cache + + + + + May be needed when changing other settings + + + + + Clear cached profiles? + + + + + + + + + + + + Continue + + + + + + + Cancel + + + + + Check the server for previously installed Amnezia services + + + + + Add them to the application if they were not displayed + + + + + Remove server from application + + + + + Remove server? + + + + + All installed AmneziaVPN services will still remain on the server. + + + + + Clear server from Amnezia software + + + + + Clear server from Amnezia software? + + + + + All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted. + + + + + PageSettingsServerInfo + + + Server name + + + + + Save + + + + + Protocols + + + + + Services + + + + + Data + + + + + PageSettingsServerProtocol + + + settings + + + + + + Remove + + + + + from server? + + + + + All users with whom you shared a connection will no longer be able to connect to it + + + + + Continue + + + + + Cancel + + + + + PageSettingsServersList + + + Servers + + + + + PageSettingsSplitTunneling + + + Only the addresses in the list must be opened via VPN + + + + + Addresses from the list should never be opened via VPN + + + + + Split site tunneling + + + + + Mode + + + + + Remove + + + + + Continue + + + + + Cancel + + + + + Site or IP + + + + + Import/Export Sites + + + + + Import + + + + + Save site list + + + + + Save sites + + + + + + + Sites files (*.json) + + + + + Import a list of sites + + + + + Replace site list + + + + + + Open sites file + + + + + Add imported sites to existing ones + + + + + PageSetupWizardConfigSource + + + Server connection + + + + + Do not use connection code from public sources. It may have been created to intercept your data. + +It's okay as long as it's from someone you trust. + + + + + What do you have? + + + + + File with connection settings or backup + + + + + File with connection settings + + + + + Open config file + + + + + QR-code + + + + + Key as text + + + + + PageSetupWizardCredentials + + + Server connection + + + + + Server IP address [:port] + + + + + 255.255.255.255:88 + + + + + Login to connect via SSH + + + + + Password / SSH private key + + + + + Continue + + + + + Ip address cannot be empty + + + + + Enter the address in the format 255.255.255.255:88 + + + + + Login cannot be empty + + + + + Password/private key cannot be empty + + + + + PageSetupWizardEasy + + + What is the level of internet control in your region? + + + + + Set up a VPN yourself + + + + + I want to choose a VPN protocol + + + + + Continue + + + + + Set up later + + + + + PageSetupWizardInstalling + + + + Usually it takes no more than 5 minutes + + + + + The server has already been added to the application + + + + + Amnesia has detected that your server is currently + + + + + busy installing other software. Amnesia installation + + + + + will pause until the server finishes installing other software + + + + + Installing + + + + + PageSetupWizardProtocolSettings + + + Installing + + + + + More detailed + + + + + Close + + + + + Network protocol + + + + + Port + + + + + Install + + + + + PageSetupWizardProtocols + + + VPN protocol + + + + + Choose the one with the highest priority for you. Later, you can install other protocols and additional services, such as DNS proxy and SFTP. + + + + + PageSetupWizardQrReader + + + Point the camera at the QR code and hold for a couple of seconds. + + + + + PageSetupWizardStart + + + Settings restored from backup file + + + + + Free service for creating a personal VPN on your server. + + + + + Helps you access blocked content without revealing your privacy, even to VPN providers. + + + + + I have the data to connect + + + + + I have nothing + + + + + PageSetupWizardTextKey + + + Connection key + + + + + A line that starts with vpn://... + + + + + Key + + + + + Insert + + + + + Continue + + + + + PageSetupWizardViewConfig + + + New connection + + + + + Do not use connection code from public sources. It could be created to intercept your data. + + + + + Collapse content + + + + + Show content + + + + + Connect + 连接 + + + + PageShare + + + Save OpenVPN config + + + + + Save WireGuard config + + + + + For the AmneziaVPN app + + + + + OpenVpn native format + + + + + WireGuard native format + + + + + VPN Access + + + + + Connection + + + + + Full access + + + + + VPN access without the ability to manage the server + + + + + Full access to server + + + + + Servers + + + + + Server + + + + + Accessing + + + + + + File with connection settings to + + + + + Protocols + + + + + Protocol + + + + + Connection to + + + + + + Connection format + + + + + Share + + + + + PopupType + + + Close + + + + + QKeychain::DeletePasswordJobPrivate + + + Password entry not found + + + + + Could not decrypt data + + + + + + Unknown error + + + + + Could not open wallet: %1; %2 + + + + + Password not found + + + + + Could not open keystore + + + + + Could not remove private key from keystore + + + + + QKeychain::JobPrivate + + + Unknown error + + + + + Access to keychain denied + + + + + QKeychain::PlainTextStore + + + Could not store data in settings: access error + + + + + Could not store data in settings: format error + + + + + Could not delete data from settings: access error + + + + + Could not delete data from settings: format error + + + + + Entry not found + + + + + QKeychain::ReadPasswordJobPrivate + + + Password entry not found + + + + + + Could not decrypt data + + + + + D-Bus is not running + + + + + + Unknown error + + + + + No keychain service available + + + + + Could not open wallet: %1; %2 + + + + + Access to keychain denied + + + + + Could not determine data type: %1; %2 + + + + + + Entry not found + + + + + Unsupported entry type 'Map' + + + + + Unknown kwallet entry type '%1' + + + + + Password not found + + + + + Could not open keystore + + + + + Could not retrieve private key from keystore + + + + + Could not create decryption cipher + + + + + QKeychain::WritePasswordJobPrivate + + + Credential size exceeds maximum size of %1 + + + + + Credential key exceeds maximum size of %1 + + + + + Writing credentials failed: Win32 error code %1 + + + + + Encryption failed + + + + + D-Bus is not running + + + + + + Unknown error + + + + + Could not open wallet: %1; %2 + + + + + Password not found + + + + + Could not open keystore + + + + + Could not create private key generator + + + + + Could not generate new private key + + + + + Could not retrieve private key from keystore + + + + + Could not create encryption cipher + + + + + Could not encrypt data + + + + + QObject + + + Sftp service + + + + + No error + + + + + Unknown Error + + + + + Function not implemented + + + + + Server check failed + + + + + Server port already used. Check for another software + + + + + Server error: Docker container missing + + + + + Server error: Docker failed + + + + + Installation canceled by user + + + + + The user does not have permission to use sudo + + + + + Ssh request was denied + + + + + Ssh request was interrupted + + + + + Ssh internal error + + + + + Invalid private key or invalid passphrase entered + + + + + The selected private key format is not supported, use openssh ED25519 key types or PEM key types + + + + + Timeout connecting to server + + + + + Sftp error: End-of-file encountered + + + + + Sftp error: File does not exist + + + + + Sftp error: Permission denied + + + + + Sftp error: Generic failure + + + + + Sftp error: Garbage received from server + + + + + Sftp error: No connection has been set up + + + + + Sftp error: There was a connection, but we lost it + + + + + Sftp error: Operation not supported by libssh yet + + + + + Sftp error: Invalid file handle + + + + + Sftp error: No such file or directory path exists + + + + + Sftp error: An attempt to create an already existing file or directory has been made + + + + + Sftp error: Write-protected filesystem + + + + + Sftp error: No media was in remote drive + + + + + Failed to save config to disk + + + + + OpenVPN config missing + + + + + OpenVPN management server error + + + + + OpenVPN executable missing + + + + + ShadowSocks (ss-local) executable missing + + + + + Cloak (ck-client) executable missing + + + + + Amnezia helper service error + + + + + OpenSSL failed + + + + + Can't connect: another VPN connection is active + + + + + Can't setup OpenVPN TAP network adapter + + + + + VPN pool error: no available addresses + + + + + The config does not contain any containers and credentiaks for connecting to the server + + + + + Internal error + + + + + IPsec + + + + + + Website in Tor network + + + + + Amnezia DNS + + + + + Sftp file sharing service + + + + + OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its own security protocol with SSL/TLS for key exchange. + + + + + ShadowSocks - masks VPN traffic, making it similar to normal web traffic, but is recognised by analysis systems in some highly censored regions. + + + + + OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against active-probbing detection. Ideal for bypassing blocking in regions with the highest levels of censorship. + + + + + WireGuard - New popular VPN protocol with high performance, high speed and low power consumption. Recommended for regions with low levels of censorship. + + + + + IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS. + + + + + Deploy a WordPress site on the Tor network in two clicks. + + + + + Replace the current DNS server with your own. This will increase your privacy level. + + + + + Creates a file vault on your server to securely store and transfer files. + + + + + OpenVPN container + + + + + Container with OpenVpn and ShadowSocks + + + + + Container with OpenVpn and ShadowSocks protocols configured with traffic masking by Cloak plugin + + + + + WireGuard container + + + + + IPsec container + + + + + DNS Service + + + + + Sftp file sharing service - is secure FTP service + + + + + Entry not found + + + + + Access to keychain denied + + + + + No keyring daemon + + + + + Already unlocked + + + + + No such keyring + + + + + Bad arguments + + + + + I/O error + + + + + Cancelled + + + + + Keyring already exists + + + + + No match + + + + + Unknown error + + + + + error 0x%1: %2 + + + + + SelectLanguageDrawer + + + Choose language + + + + + Settings + + + Server #1 + + + + + + Server + + + + + SettingsController + + + Software version + + + + + Backup file is corrupted + + + + + All settings have been reset to default values + + + + + Cached profiles cleared + + + + + ShareConnectionDrawer + + + + Save AmneziaVPN config + + + + + Share + + + + + Copy + + + + + Copied + + + + + Show content + + + + + To read the QR code in the Amnezia app, select "Add server" → "I have data to connect" → "QR code, key or settings file" + + + + + SitesController + + + Hostname not look like ip adress or domain name + + + + + New site added: + + + + + Site removed: + + + + + Can't open file: + + + + + Failed to parse JSON data from file: + + + + + The JSON data is not an array in file: + + + + + Import completed + + + + + Export completed + + + + + SystemTrayNotificationHandler + + + Show + + + + + Connect + 连接 + + + + Disconnect + + + + + Visit Website + + + + + Quit + + + + + VpnConnection + + + Mbps + + + + + VpnProtocol + + + Unknown + + + + + Disconnected + + + + + Preparing + + + + + Connecting... + + + + + Connected + 已连接 + + + + Disconnecting... + + + + + Reconnecting... + + + + + Error + + + + + amnezia::ContainerProps + + + Low + + + + + High + + + + + Medium + + + + + I just want to increase the level of privacy + + + + + Many foreign websites and VPN providers are blocked + + + + + Some foreign sites are blocked, but VPN providers are not blocked + + + + + main2 + + + Private key passphrase + + + + + Save + + + + diff --git a/client/ui/models/languageModel.cpp b/client/ui/models/languageModel.cpp index 5135f348..b860b9da 100644 --- a/client/ui/models/languageModel.cpp +++ b/client/ui/models/languageModel.cpp @@ -6,7 +6,8 @@ LanguageModel::LanguageModel(std::shared_ptr settings, QObject *parent QMetaEnum metaEnum = QMetaEnum::fromType(); for (int i = 0; i < metaEnum.keyCount(); i++) { m_availableLanguages.push_back( - LanguageModelData { metaEnum.valueToKey(i), static_cast(i) }); + LanguageModelData {getLocalLanguageName(static_cast(i)), + static_cast(i) }); } } @@ -36,11 +37,26 @@ QHash LanguageModel::roleNames() const return roles; } +QString LanguageModel::getLocalLanguageName(const LanguageSettings::AvailableLanguageEnum language) +{ + QString strLanguage(""); + switch (language) { + case LanguageSettings::AvailableLanguageEnum::English: strLanguage = "English"; break; + case LanguageSettings::AvailableLanguageEnum::Russian: strLanguage = "Русский"; break; + case LanguageSettings::AvailableLanguageEnum::China_cn: strLanguage = "\347\256\200\344\275\223\344\270\255\346\226\207"; break; + default: + break; + } + + return strLanguage; +} + void LanguageModel::changeLanguage(const LanguageSettings::AvailableLanguageEnum language) { switch (language) { case LanguageSettings::AvailableLanguageEnum::English: emit updateTranslations(QLocale::English); break; case LanguageSettings::AvailableLanguageEnum::Russian: emit updateTranslations(QLocale::Russian); break; + case LanguageSettings::AvailableLanguageEnum::China_cn: emit updateTranslations(QLocale::Chinese); break; default: emit updateTranslations(QLocale::English); break; } } @@ -51,6 +67,7 @@ int LanguageModel::getCurrentLanguageIndex() switch (locale.language()) { case QLocale::English: return static_cast(LanguageSettings::AvailableLanguageEnum::English); break; case QLocale::Russian: return static_cast(LanguageSettings::AvailableLanguageEnum::Russian); break; + case QLocale::Chinese: return static_cast(LanguageSettings::AvailableLanguageEnum::China_cn); break; default: return static_cast(LanguageSettings::AvailableLanguageEnum::English); break; } } diff --git a/client/ui/models/languageModel.h b/client/ui/models/languageModel.h index b64862dd..c8879a34 100644 --- a/client/ui/models/languageModel.h +++ b/client/ui/models/languageModel.h @@ -11,7 +11,8 @@ namespace LanguageSettings Q_NAMESPACE enum class AvailableLanguageEnum { English, - Russian + Russian, + China_cn }; Q_ENUM_NS(AvailableLanguageEnum) @@ -59,6 +60,8 @@ protected: QHash roleNames() const override; private: + QString getLocalLanguageName(const LanguageSettings::AvailableLanguageEnum language); + QVector m_availableLanguages; std::shared_ptr m_settings; From 2986a18c8f3bc4bc54e2bfba97511f117f821cfe Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Thu, 28 Sep 2023 23:54:32 +0300 Subject: [PATCH 149/278] iOS AWG support --- .gitmodules | 6 +++--- client/3rd/awg-apple | 1 + client/3rd/wireguard-apple | 1 - client/cmake/ios.cmake | 2 +- client/ios/networkextension/CMakeLists.txt | 2 +- .../WireGuardNetworkExtension-Bridging-Header.h | 4 ++-- client/macos/app/WireGuard-Bridging-Header.h | 2 +- .../WireGuardNetworkExtension-Bridging-Header.h | 2 +- client/platforms/ios/WireGuard-Bridging-Header.h | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) create mode 160000 client/3rd/awg-apple delete mode 160000 client/3rd/wireguard-apple diff --git a/.gitmodules b/.gitmodules index 453a8ee4..c96dd6bc 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "client/3rd/wireguard-apple"] - path = client/3rd/wireguard-apple - url = https://github.com/WireGuard/wireguard-apple [submodule "client/3rd/OpenVPNAdapter"] path = client/3rd/OpenVPNAdapter url = https://github.com/amnezia-vpn/OpenVPNAdapter.git @@ -25,3 +22,6 @@ [submodule "client/3rd-prebuilt"] path = client/3rd-prebuilt url = https://github.com/amnezia-vpn/3rd-prebuilt +[submodule "client/3rd/awg-apple"] + path = client/3rd/awg-apple + url = https://github.com/amnezia-vpn/awg-apple diff --git a/client/3rd/awg-apple b/client/3rd/awg-apple new file mode 160000 index 00000000..5767a03f --- /dev/null +++ b/client/3rd/awg-apple @@ -0,0 +1 @@ +Subproject commit 5767a03f75a2b77d4f78fdd77ff51a1eefabe3b0 diff --git a/client/3rd/wireguard-apple b/client/3rd/wireguard-apple deleted file mode 160000 index 23618f99..00000000 --- a/client/3rd/wireguard-apple +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 23618f994f17d8ad8f2f65d79b4a1e8a0830b334 diff --git a/client/cmake/ios.cmake b/client/cmake/ios.cmake index 5dc1b2e7..7aa9f1a9 100644 --- a/client/cmake/ios.cmake +++ b/client/cmake/ios.cmake @@ -97,7 +97,7 @@ target_compile_options(${PROJECT} PRIVATE -DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\" ) -set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/wireguard-apple/Sources) +set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/awg-apple/Sources) target_sources(${PROJECT} PRIVATE # ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosvpnprotocol.swift diff --git a/client/ios/networkextension/CMakeLists.txt b/client/ios/networkextension/CMakeLists.txt index 29dc0bbe..16769ea3 100644 --- a/client/ios/networkextension/CMakeLists.txt +++ b/client/ios/networkextension/CMakeLists.txt @@ -58,7 +58,7 @@ target_link_libraries(networkextension PRIVATE ${FW_UI_KIT}) target_compile_options(networkextension PRIVATE -DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\") target_compile_options(networkextension PRIVATE -DNETWORK_EXTENSION=1) -set(WG_APPLE_SOURCE_DIR ${CLIENT_ROOT_DIR}/3rd/wireguard-apple/Sources) +set(WG_APPLE_SOURCE_DIR ${CLIENT_ROOT_DIR}/3rd/awg-apple/Sources) target_sources(networkextension PRIVATE ${WG_APPLE_SOURCE_DIR}/WireGuardKit/WireGuardAdapter.swift diff --git a/client/ios/networkextension/WireGuardNetworkExtension-Bridging-Header.h b/client/ios/networkextension/WireGuardNetworkExtension-Bridging-Header.h index 03a987ad..44d0b6b0 100644 --- a/client/ios/networkextension/WireGuardNetworkExtension-Bridging-Header.h +++ b/client/ios/networkextension/WireGuardNetworkExtension-Bridging-Header.h @@ -1,6 +1,6 @@ #include "wireguard-go-version.h" -#include "3rd/wireguard-apple/Sources/WireGuardKitGo/wireguard.h" -#include "3rd/wireguard-apple/Sources/WireGuardKitC/WireGuardKitC.h" +#include "3rd/awg-apple/Sources/WireGuardKitGo/wireguard.h" +#include "3rd/awg-apple/Sources/WireGuardKitC/WireGuardKitC.h" #include #include diff --git a/client/macos/app/WireGuard-Bridging-Header.h b/client/macos/app/WireGuard-Bridging-Header.h index 40b6c89d..da71002d 100644 --- a/client/macos/app/WireGuard-Bridging-Header.h +++ b/client/macos/app/WireGuard-Bridging-Header.h @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "wireguard-go-version.h" -#include "3rd/wireguard-apple/Sources/WireGuardKitC/WireGuardKitC.h" +#include "3rd/awg-apple/Sources/WireGuardKitC/WireGuardKitC.h" #include #include diff --git a/client/macos/networkextension/WireGuardNetworkExtension-Bridging-Header.h b/client/macos/networkextension/WireGuardNetworkExtension-Bridging-Header.h index 8a437ce0..ea5c8e38 100644 --- a/client/macos/networkextension/WireGuardNetworkExtension-Bridging-Header.h +++ b/client/macos/networkextension/WireGuardNetworkExtension-Bridging-Header.h @@ -4,7 +4,7 @@ #include "macos/gobridge/wireguard.h" #include "wireguard-go-version.h" -#include "3rd/wireguard-apple/Sources/WireGuardKitC/WireGuardKitC.h" +#include "3rd/awg-apple/Sources/WireGuardKitC/WireGuardKitC.h" #include "3rd/ShadowSocks/ShadowSocks/ShadowSocks.h" #include "platforms/ios/ssconnectivity.h" #include "platforms/ios/iosopenvpn2ssadapter.h" diff --git a/client/platforms/ios/WireGuard-Bridging-Header.h b/client/platforms/ios/WireGuard-Bridging-Header.h index e5dfa39f..fbccb2d4 100644 --- a/client/platforms/ios/WireGuard-Bridging-Header.h +++ b/client/platforms/ios/WireGuard-Bridging-Header.h @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -#include "3rd/wireguard-apple/Sources/WireGuardKitC/WireGuardKitC.h" +#include "3rd/awg-apple/Sources/WireGuardKitC/WireGuardKitC.h" #include #include From 54b45a36e124176fe05ff620ef43ff9fccbd2c3f Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 29 Sep 2023 18:41:00 +0500 Subject: [PATCH 150/278] test configuration using wg instead of wg-quick to configure the server --- client/server_scripts/amnezia_wireguard/Dockerfile | 2 +- .../amnezia_wireguard/configure_container.sh | 2 +- client/server_scripts/amnezia_wireguard/start.sh | 7 ++++--- client/server_scripts/build_container.sh | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/client/server_scripts/amnezia_wireguard/Dockerfile b/client/server_scripts/amnezia_wireguard/Dockerfile index ed974dc6..8c536fc7 100644 --- a/client/server_scripts/amnezia_wireguard/Dockerfile +++ b/client/server_scripts/amnezia_wireguard/Dockerfile @@ -3,7 +3,7 @@ FROM amneziavpn/amnezia-wg:latest LABEL maintainer="AmneziaVPN" #Install required packages -RUN apk add --no-cache curl wireguard-tools dumb-init +RUN apk add --no-cache bash curl dumb-init RUN apk --update upgrade --no-cache RUN mkdir -p /opt/amnezia diff --git a/client/server_scripts/amnezia_wireguard/configure_container.sh b/client/server_scripts/amnezia_wireguard/configure_container.sh index 8653a932..fa7b09f9 100644 --- a/client/server_scripts/amnezia_wireguard/configure_container.sh +++ b/client/server_scripts/amnezia_wireguard/configure_container.sh @@ -12,7 +12,7 @@ echo $WIREGUARD_PSK > /opt/amnezia/amneziawireguard/wireguard_psk.key cat > /opt/amnezia/amneziawireguard/wg0.conf < Date: Sat, 30 Sep 2023 00:58:08 +0300 Subject: [PATCH 151/278] iOS AWG protocol Setup --- client/3rd-prebuilt | 2 +- client/containers/containers_defs.cpp | 1 + client/platforms/ios/ios_controller.h | 1 + client/platforms/ios/ios_controller.mm | 12 ++++++++++++ 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/client/3rd-prebuilt b/client/3rd-prebuilt index e8795854..6f0d654a 160000 --- a/client/3rd-prebuilt +++ b/client/3rd-prebuilt @@ -1 +1 @@ -Subproject commit e8795854a5cf27004fe78caecc90a961688d1d41 +Subproject commit 6f0d654a2409e2f634e7f7b95d34998c8eba2d7b diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index 21f7b044..0b9e44a2 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -186,6 +186,7 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c) switch (c) { case DockerContainer::WireGuard: return true; case DockerContainer::OpenVpn: return true; + case DockerContainer::AmneziaWireGuard: return true; case DockerContainer::Cloak: return true; // case DockerContainer::ShadowSocks: return true; diff --git a/client/platforms/ios/ios_controller.h b/client/platforms/ios/ios_controller.h index ea8adbc0..6d10dc08 100644 --- a/client/platforms/ios/ios_controller.h +++ b/client/platforms/ios/ios_controller.h @@ -62,6 +62,7 @@ private: bool setupOpenVPN(); bool setupCloak(); bool setupWireGuard(); + bool setupAmneziaWireGuard(); bool startOpenVPN(const QString &config); bool startWireGuard(const QString &jsonConfig); diff --git a/client/platforms/ios/ios_controller.mm b/client/platforms/ios/ios_controller.mm index 57394383..6782c8da 100644 --- a/client/platforms/ios/ios_controller.mm +++ b/client/platforms/ios/ios_controller.mm @@ -204,6 +204,9 @@ bool IosController::connectVpn(amnezia::Proto proto, const QJsonObject& configur if (proto == amnezia::Proto::WireGuard) { return setupWireGuard(); } + if (proto == amnezia::Proto::AmneziaWireGuard) { + return setupAmneziaWireGuard(); + } return false; } @@ -307,6 +310,15 @@ bool IosController::setupWireGuard() return startWireGuard(wgConfig); } +bool IosController::setupAmneziaWireGuard() +{ + QJsonObject config = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::AmneziaWireGuard)].toObject(); + + QString wgConfig = config[config_key::config].toString(); + + return startWireGuard(wgConfig); +} + bool IosController::startOpenVPN(const QString &config) { qDebug() << "IosController::startOpenVPN"; From 5535b6a6e3cdd83db8cb993d34ab71485a500cb9 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Sat, 30 Sep 2023 17:36:06 +0800 Subject: [PATCH 152/278] embedded qm files into qrc file --- client/CMakeLists.txt | 40 +++++++++++++------------ client/amnezia_application.cpp | 7 +++-- client/translations/amneziavpn_ru.ts | 2 +- client/translations/amneziavpn_zh_CN.ts | 2 +- client/translations/translations.qrc.in | 5 ++++ 5 files changed, 32 insertions(+), 24 deletions(-) create mode 100644 client/translations/translations.qrc.in diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index f149e204..21f4513e 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -50,29 +50,31 @@ endif() qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc) -set(AMNEZIAVPN_TR_FILES +# -- i18n begin +set(CMAKE_AUTORCC ON) + +set(AMNEZIAVPN_TS_FILES ${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ru.ts ${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_zh_CN.ts ) -file(GLOB_RECURSE AMNEZIAVPN_TR_SOURCES *.qrc *.cpp *.h *.ui) -if ( BUILD_TRANSLATIONS ) - qt_create_translation(AMNEZIAVPN_MESSAGES ${AMNEZIAVPN_TR_SOURCES} ${AMNEZIAVPN_TR_FILES}) - qt_add_translation(AMNEZIAVPN_QM_FILES ${AMNEZIAVPN_TR_FILES}) - add_custom_target(amnezia_messages DEPENDS ${AMNEZIAVPN_MESSAGES}) - add_custom_target(amnezia_translations DEPENDS ${AMNEZIAVPN_QM_FILES} amnezia_messages) - add_dependencies(${PROJECT} amnezia_translations) +file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui) - if (BUILD_TRANSLATIONS_AS_RESOURCES) - set(QM_FILE_LIST "") - foreach(FILE ${AMNEZIAVPN_QM_FILES}) - list(APPEND QM_FILE_LIST "${FILE}") - endforeach() - string(REPLACE ";" "" QM_FILE_LIST ${QM_FILE_LIST}) - configure_file(${CMAKE_CURRENT_LIST_DIR}/translations/translations.qrc ${CMAKE_CURRENT_LIST_DIR}/translations.qrc) - target_sources(${PROJECT} PRIVATE ${CMAKE_CURRENT_LIST_DIR}/translations.qrc) - endif() -endif( BUILD_TRANSLATIONS ) +qt_create_translation(AMNEZIAVPN_QM_FILES ${AMNEZIAVPN_TS_SOURCES} ${AMNEZIAVPN_TS_FILES}) +#add_custom_target(amnezia_messages DEPENDS ${AMNEZIAVPN_MESSAGES}) +#add_custom_target(amnezia_translations DEPENDS ${AMNEZIAVPN_QM_FILES} amnezia_messages) +#add_dependencies(${PROJECT} amnezia_translations) + +set(QM_FILE_LIST "") +foreach(FILE ${AMNEZIAVPN_QM_FILES}) + get_filename_component(QM_FILE_NAME ${FILE} NAME) + list(APPEND QM_FILE_LIST "${QM_FILE_NAME}") +endforeach() +string(REPLACE ";" "" QM_FILE_LIST ${QM_FILE_LIST}) + +configure_file(${CMAKE_CURRENT_LIST_DIR}/translations/translations.qrc.in ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc) +qt6_add_resources(QRC ${I18NQRC} ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc) +# -- i18n end if(IOS) #execute_process(COMMAND bash ${CMAKE_CURRENT_LIST_DIR}/scripts/run-build-cloak.sh) @@ -342,5 +344,5 @@ if(NOT IOS AND NOT ANDROID) endif() -target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC}) +target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC}) qt_finalize_target(${PROJECT}) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index d87f326e..6203e3af 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -227,15 +227,16 @@ void AmneziaApplication::loadTranslator() void AmneziaApplication::updateTranslator(const QLocale &locale) { - QResource::registerResource(":/translations.qrc"); if (!m_translator->isEmpty()) { QCoreApplication::removeTranslator(m_translator.get()); } m_settings->setAppLanguage(locale); - QString strFileName = QString("amneziavpn")+QLatin1String("_")+locale.name()+".qm"; - if (m_translator->load(strFileName, "../../../")) { + QString strFileName = QString(":/translations/amneziavpn")+QLatin1String("_")+locale.name()+".qm"; + + if (m_translator->load(strFileName)) { + qDebug() << "yyyyyyxxxxxxxx--------------" <setAppLanguage(locale); } diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index e6c60a33..82393e38 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -4,7 +4,7 @@ AmneziaApplication - + Split tunneling for WireGuard is not implemented, the option was disabled diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index 257a3378..c70b0f75 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -4,7 +4,7 @@ AmneziaApplication - + Split tunneling for WireGuard is not implemented, the option was disabled diff --git a/client/translations/translations.qrc.in b/client/translations/translations.qrc.in new file mode 100644 index 00000000..f49df661 --- /dev/null +++ b/client/translations/translations.qrc.in @@ -0,0 +1,5 @@ + + + @QM_FILE_LIST@ + + From 07d7fac49059d4630b0917a5dd1ea56b58a1f4da Mon Sep 17 00:00:00 2001 From: ronoaer Date: Sat, 30 Sep 2023 17:40:26 +0800 Subject: [PATCH 153/278] removed invalid code --- client/CMakeLists.txt | 3 --- client/amnezia_application.cpp | 2 -- 2 files changed, 5 deletions(-) diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 21f4513e..cc192268 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -61,9 +61,6 @@ set(AMNEZIAVPN_TS_FILES file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui) qt_create_translation(AMNEZIAVPN_QM_FILES ${AMNEZIAVPN_TS_SOURCES} ${AMNEZIAVPN_TS_FILES}) -#add_custom_target(amnezia_messages DEPENDS ${AMNEZIAVPN_MESSAGES}) -#add_custom_target(amnezia_translations DEPENDS ${AMNEZIAVPN_QM_FILES} amnezia_messages) -#add_dependencies(${PROJECT} amnezia_translations) set(QM_FILE_LIST "") foreach(FILE ${AMNEZIAVPN_QM_FILES}) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 6203e3af..41a541ac 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -234,9 +234,7 @@ void AmneziaApplication::updateTranslator(const QLocale &locale) m_settings->setAppLanguage(locale); QString strFileName = QString(":/translations/amneziavpn")+QLatin1String("_")+locale.name()+".qm"; - if (m_translator->load(strFileName)) { - qDebug() << "yyyyyyxxxxxxxx--------------" <setAppLanguage(locale); } From 4ed153373f585d93ddf3c566ba63fbf7cc43cba3 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Sat, 30 Sep 2023 16:05:23 -0400 Subject: [PATCH 154/278] Fix Linux build, some naming changes --- .../{amneziaWireGuardConfigurator.cpp => awg_configurator.cpp} | 2 +- .../{amneziaWireGuardConfigurator.h => awg_configurator.h} | 0 client/configurators/vpn_configurator.cpp | 2 +- ...mneziaWireGuardProtocol.cpp => amneziawireguardprotocol.cpp} | 2 +- .../{amneziaWireGuardProtocol.h => amneziawireguardprotocol.h} | 0 5 files changed, 3 insertions(+), 3 deletions(-) rename client/configurators/{amneziaWireGuardConfigurator.cpp => awg_configurator.cpp} (98%) rename client/configurators/{amneziaWireGuardConfigurator.h => awg_configurator.h} (100%) rename client/protocols/{amneziaWireGuardProtocol.cpp => amneziawireguardprotocol.cpp} (84%) rename client/protocols/{amneziaWireGuardProtocol.h => amneziawireguardprotocol.h} (100%) diff --git a/client/configurators/amneziaWireGuardConfigurator.cpp b/client/configurators/awg_configurator.cpp similarity index 98% rename from client/configurators/amneziaWireGuardConfigurator.cpp rename to client/configurators/awg_configurator.cpp index 3ed27208..85dbd6de 100644 --- a/client/configurators/amneziaWireGuardConfigurator.cpp +++ b/client/configurators/awg_configurator.cpp @@ -1,4 +1,4 @@ -#include "amneziaWireGuardConfigurator.h" +#include "awg_configurator.h" #include #include diff --git a/client/configurators/amneziaWireGuardConfigurator.h b/client/configurators/awg_configurator.h similarity index 100% rename from client/configurators/amneziaWireGuardConfigurator.h rename to client/configurators/awg_configurator.h diff --git a/client/configurators/vpn_configurator.cpp b/client/configurators/vpn_configurator.cpp index 6706deed..8ab43499 100644 --- a/client/configurators/vpn_configurator.cpp +++ b/client/configurators/vpn_configurator.cpp @@ -5,7 +5,7 @@ #include "shadowsocks_configurator.h" #include "ssh_configurator.h" #include "wireguard_configurator.h" -#include "amneziaWireGuardConfigurator.h" +#include "awg_configurator.h" #include #include diff --git a/client/protocols/amneziaWireGuardProtocol.cpp b/client/protocols/amneziawireguardprotocol.cpp similarity index 84% rename from client/protocols/amneziaWireGuardProtocol.cpp rename to client/protocols/amneziawireguardprotocol.cpp index b4c5b430..cab03da9 100644 --- a/client/protocols/amneziaWireGuardProtocol.cpp +++ b/client/protocols/amneziawireguardprotocol.cpp @@ -1,4 +1,4 @@ -#include "amneziaWireGuardProtocol.h" +#include "amneziawireguardprotocol.h" AmneziaWireGuardProtocol::AmneziaWireGuardProtocol(const QJsonObject &configuration, QObject *parent) : WireguardProtocol(configuration, parent) diff --git a/client/protocols/amneziaWireGuardProtocol.h b/client/protocols/amneziawireguardprotocol.h similarity index 100% rename from client/protocols/amneziaWireGuardProtocol.h rename to client/protocols/amneziawireguardprotocol.h From eaede032b4fe3ab940a22aad456aea0063a77584 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Sun, 1 Oct 2023 11:12:27 +0800 Subject: [PATCH 155/278] 1. updated memory text when language changed, 2. updated initialize index --- client/amnezia_application.cpp | 9 +++++---- client/main.cpp | 1 - client/ui/controllers/connectionController.cpp | 14 ++++++++++++++ client/ui/controllers/connectionController.h | 6 ++++++ client/ui/qml/Pages2/PageStart.qml | 2 ++ 5 files changed, 27 insertions(+), 5 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 41a541ac..01b37229 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -87,6 +87,7 @@ void AmneziaApplication::init() m_vpnConnectionThread.start(); initModels(); + loadTranslator(); initControllers(); #ifdef Q_OS_ANDROID @@ -231,17 +232,17 @@ void AmneziaApplication::updateTranslator(const QLocale &locale) QCoreApplication::removeTranslator(m_translator.get()); } - m_settings->setAppLanguage(locale); - QString strFileName = QString(":/translations/amneziavpn")+QLatin1String("_")+locale.name()+".qm"; if (m_translator->load(strFileName)) { if (QCoreApplication::installTranslator(m_translator.get())) { m_settings->setAppLanguage(locale); } - - m_engine->retranslate(); + } else { + m_settings->setAppLanguage(QLocale::English); } + m_engine->retranslate(); + emit translationsUpdated(); } diff --git a/client/main.cpp b/client/main.cpp index 53c3238f..396b7625 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -59,7 +59,6 @@ int main(int argc, char *argv[]) if (doExec) { app.init(); - app.loadTranslator(); qInfo().noquote() << QString("Started %1 version %2").arg(APPLICATION_NAME, APP_VERSION); qInfo().noquote() << QString("%1 (%2)").arg(QSysInfo::prettyProductName(), QSysInfo::currentCpuArchitecture()); diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index 77ca0f5f..8df62a94 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -19,6 +19,8 @@ ConnectionController::ConnectionController(const QSharedPointer &s Qt::QueuedConnection); connect(this, &ConnectionController::disconnectFromVpn, m_vpnConnection.get(), &VpnConnection::disconnectFromVpn, Qt::QueuedConnection); + + m_state = Vpn::ConnectionState::Disconnected; } ConnectionController::~ConnectionController() @@ -70,6 +72,8 @@ QString ConnectionController::getLastConnectionError() void ConnectionController::onConnectionStateChanged(Vpn::ConnectionState state) { + m_state = state; + m_isConnected = false; m_connectionStateText = tr("Connection..."); switch (state) { @@ -126,6 +130,16 @@ void ConnectionController::onCurrentContainerUpdated() } } +void ConnectionController::translateMemoryText() +{ + onConnectionStateChanged(getCurrentConnectionState()); +} + +Vpn::ConnectionState ConnectionController::getCurrentConnectionState() +{ + return m_state; +} + QString ConnectionController::connectionStateText() const { return m_connectionStateText; diff --git a/client/ui/controllers/connectionController.h b/client/ui/controllers/connectionController.h index 7bfe0fac..c1eaf38f 100644 --- a/client/ui/controllers/connectionController.h +++ b/client/ui/controllers/connectionController.h @@ -34,6 +34,8 @@ public slots: void onCurrentContainerUpdated(); + void translateMemoryText(); + signals: void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig); @@ -44,6 +46,8 @@ signals: void reconnectWithUpdatedContainer(const QString &message); private: + Vpn::ConnectionState getCurrentConnectionState(); + QSharedPointer m_serversModel; QSharedPointer m_containersModel; @@ -52,6 +56,8 @@ private: bool m_isConnected = false; bool m_isConnectionInProgress = false; QString m_connectionStateText = tr("Connect"); + + Vpn::ConnectionState m_state; }; #endif // CONNECTIONCONTROLLER_H diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 43366af7..f36aac41 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -171,6 +171,8 @@ PageType { onClicked: { tabBarStackView.goToTabBarPage(PageEnum.PageHome) ServersModel.currentlyProcessedIndex = ServersModel.defaultIndex + + ConnectionController.translateMemoryText() } } TabImageButtonType { From 784ae0da53b1eb738a827f670d7acb257cb75ab7 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Sun, 1 Oct 2023 12:11:13 +0800 Subject: [PATCH 156/278] updated visible logic of button 'set up later' --- client/ui/controllers/pageController.cpp | 12 ++++++++++++ client/ui/controllers/pageController.h | 6 ++++++ client/ui/qml/Components/ConnectButton.qml | 2 ++ client/ui/qml/Pages2/PageSetupWizardEasy.qml | 11 ++++++++++- 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/pageController.cpp index 7b8f74ab..2a14548c 100644 --- a/client/ui/controllers/pageController.cpp +++ b/client/ui/controllers/pageController.cpp @@ -37,6 +37,8 @@ PageController::PageController(const QSharedPointer &serversModel, connect(this, &PageController::raiseMainWindow, []() { setDockIconVisible(true); }); connect(this, &PageController::hideMainWindow, []() { setDockIconVisible(false); }); #endif + + m_bConnectTrigger = false; } QString PageController::getInitialPage() @@ -145,3 +147,13 @@ void PageController::drawerClose() m_drawerLayer = 0; } } + +bool PageController::isConnectTrigger() +{ + return m_bConnectTrigger; +} + +void PageController::setConnectTrigger(bool trigger) +{ + m_bConnectTrigger = trigger; +} diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index a8f883fe..d16c36a6 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -84,6 +84,10 @@ public slots: void drawerOpen(); void drawerClose(); + + bool isConnectTrigger(); + void setConnectTrigger(bool trigger); + signals: void goToPage(PageLoader::PageEnum page, bool slide = true); void goToStartPage(); @@ -120,6 +124,8 @@ private: PageLoader::PageEnum m_currentRootPage; int m_drawerLayer; + + bool m_bConnectTrigger; }; #endif // PAGECONTROLLER_H diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml index b7484c73..b5c94ea8 100644 --- a/client/ui/qml/Components/ConnectButton.qml +++ b/client/ui/qml/Components/ConnectButton.qml @@ -139,6 +139,8 @@ Button { onClicked: { if (!ContainersModel.isAnyContainerInstalled()) { + PageController.setConnectTrigger(true) + ServersModel.currentlyProcessedIndex = ServersModel.getDefaultServerIndex() PageController.goToPage(PageEnum.PageSetupWizardEasy) diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index 4f94e985..08542b23 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -183,7 +183,16 @@ PageType { textColor: "#D7D8DB" borderWidth: 1 - visible: ContainersModel.isAnyContainerInstalled() + visible: { + if (PageController.isConnectTrigger()) { + PageController.setConnectTrigger(false) + + return ContainersModel.isAnyContainerInstalled() + } + + + return true + } text: qsTr("Set up later") From 4e3955b39d1c22e86d81d238a8e037dbb3c9e61e Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sun, 1 Oct 2023 12:06:50 +0300 Subject: [PATCH 157/278] Added switcher "Allow app screenshots" for android --- client/settings.h | 9 ++++ client/ui/controllers/importController.cpp | 9 ---- client/ui/controllers/settingsController.cpp | 42 +++++++++++++++++++ client/ui/controllers/settingsController.h | 3 ++ .../ui/qml/Pages2/PageSettingsApplication.qml | 20 +++++++++ 5 files changed, 74 insertions(+), 9 deletions(-) diff --git a/client/settings.h b/client/settings.h index 919ca731..f530f6c5 100644 --- a/client/settings.h +++ b/client/settings.h @@ -178,6 +178,15 @@ public: m_settings.setValue("Conf/appLanguage", locale); }; + bool isScreenshotsEnabled() const + { + return m_settings.value("Conf/screenshotsEnabled", false).toBool(); + } + void setScreenshotsEnabled(bool enabled) + { + m_settings.setValue("Conf/screenshotsEnabled", enabled); + } + void clearSettings(); signals: diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index d9278ece..aaea8db3 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -61,15 +61,6 @@ ImportController::ImportController(const QSharedPointer &serversMo { #ifdef Q_OS_ANDROID mInstance = this; - // Set security screen for Android app - AndroidUtils::runOnAndroidThreadSync([]() { - QJniObject activity = AndroidUtils::getActivity(); - QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;"); - if (window.isValid()) { - const int FLAG_SECURE = 8192; - window.callMethod("addFlags", "(I)V", FLAG_SECURE); - } - }); AndroidUtils::runOnAndroidThreadAsync([]() { JNINativeMethod methods[] { diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index 46993f6a..3edfe3d9 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -6,6 +6,11 @@ #include "systemController.h" #include "ui/qautostart.h" #include "version.h" +#ifdef Q_OS_ANDROID + #include "../../platforms/android/android_controller.h" + #include "../../platforms/android/androidutils.h" + #include +#endif SettingsController::SettingsController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, @@ -18,6 +23,20 @@ SettingsController::SettingsController(const QSharedPointer &serve m_settings(settings) { m_appVersion = QString("%1: %2 (%3)").arg(tr("Software version"), QString(APP_MAJOR_VERSION), __DATE__); + +#ifdef Q_OS_ANDROID + if (!m_settings->isScreenshotsEnabled()) { + // Set security screen for Android app + AndroidUtils::runOnAndroidThreadSync([]() { + QJniObject activity = AndroidUtils::getActivity(); + QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;"); + if (window.isValid()) { + const int FLAG_SECURE = 8192; + window.callMethod("addFlags", "(I)V", FLAG_SECURE); + } + }); + } +#endif } void SettingsController::toggleAmneziaDns(bool enable) @@ -152,3 +171,26 @@ void SettingsController::toggleStartMinimized(bool enable) { m_settings->setStartMinimized(enable); } + +bool SettingsController::isScreenshotsEnabled() +{ + return m_settings->isScreenshotsEnabled(); +} + +void SettingsController::toggleScreenshotsEnabled(bool enable) +{ + m_settings->setScreenshotsEnabled(enable); +#ifdef Q_OS_ANDROID + std::string command = enable ? "clearFlags" : "addFlags"; + + // Set security screen for Android app + AndroidUtils::runOnAndroidThreadSync([&command]() { + QJniObject activity = AndroidUtils::getActivity(); + QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;"); + if (window.isValid()) { + const int FLAG_SECURE = 8192; + window.callMethod(command.c_str(), "(I)V", FLAG_SECURE); + } + }); +#endif +} \ No newline at end of file diff --git a/client/ui/controllers/settingsController.h b/client/ui/controllers/settingsController.h index 5a51a3b4..be041f3e 100644 --- a/client/ui/controllers/settingsController.h +++ b/client/ui/controllers/settingsController.h @@ -54,6 +54,9 @@ public slots: bool isStartMinimizedEnabled(); void toggleStartMinimized(bool enable); + bool isScreenshotsEnabled(); + void toggleScreenshotsEnabled(bool enable); + signals: void primaryDnsChanged(); void secondaryDnsChanged(); diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index 6f5e48a2..c5536fdb 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -43,6 +43,26 @@ PageType { headerText: qsTr("Application") } + SwitcherType { + visible: GC.isMobile() + + Layout.fillWidth: true + Layout.margins: 16 + + text: qsTr("Allow application screenshots") + + checked: SettingsController.isScreenshotsEnabled() + onCheckedChanged: { + if (checked !== SettingsController.isScreenshotsEnabled()) { + SettingsController.toggleScreenshotsEnabled(checked) + } + } + } + + DividerType { + visible: GC.isMobile() + } + SwitcherType { visible: !GC.isMobile() From 39c2124a26d1401fa4434fb790ff2780f5a20d84 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sun, 1 Oct 2023 21:43:30 +0500 Subject: [PATCH 158/278] returned the awg setting via wg-quick --- client/server_scripts/amnezia_wireguard/start.sh | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/client/server_scripts/amnezia_wireguard/start.sh b/client/server_scripts/amnezia_wireguard/start.sh index 505ce53e..b371d5b5 100644 --- a/client/server_scripts/amnezia_wireguard/start.sh +++ b/client/server_scripts/amnezia_wireguard/start.sh @@ -6,11 +6,10 @@ echo "Container startup" #ifconfig eth0:0 $SERVER_IP_ADDRESS netmask 255.255.255.255 up # kill daemons in case of restart -# wg-quick down /opt/amnezia/amneziawireguard/wg0.conf +wg-quick down /opt/amnezia/amneziawireguard/wg0.conf -/usr/bin/amnezia-wg wg0 && /usr/bin/wg setconf wg0 /opt/amnezia/amneziawireguard/wg0.conf && ip address add dev wg0 10.8.1.1/24 && ip link set up dev wg0 -# # # start daemons if configured -# # if [ -f /opt/amnezia/amneziawireguard/wg0.conf ]; then (wg-quick up /opt/amnezia/amneziawireguard/wg0.conf); fi +# start daemons if configured +if [ -f /opt/amnezia/amneziawireguard/wg0.conf ]; then (wg-quick up /opt/amnezia/amneziawireguard/wg0.conf); fi # Allow traffic on the TUN interface. iptables -A INPUT -i wg0 -j ACCEPT From 3d999a503c865402cd38c22713071e001cc4f614 Mon Sep 17 00:00:00 2001 From: Matthew Schwiebert Date: Sun, 1 Oct 2023 20:35:05 -0400 Subject: [PATCH 159/278] Add custom drawer behavior to pageHome, for mobile and desktop --- client/ui/qml/Controls2/DrawerType.qml | 23 ++-- client/ui/qml/Pages2/PageHome.qml | 156 ++++++++++++++++++------- 2 files changed, 121 insertions(+), 58 deletions(-) diff --git a/client/ui/qml/Controls2/DrawerType.qml b/client/ui/qml/Controls2/DrawerType.qml index bb2ee4aa..4ea24091 100644 --- a/client/ui/qml/Controls2/DrawerType.qml +++ b/client/ui/qml/Controls2/DrawerType.qml @@ -5,7 +5,6 @@ Drawer { id: drawer property bool needCloseButton: true property bool isOpened: false - property int pageHeight Connections { target: PageController @@ -51,26 +50,23 @@ Drawer { if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) { PageController.updateNavigationBarColor(0xFF1C1D21) } - - if (needCloseButton) { - PageController.drawerOpen() - } - position = (dragMargin / pageHeight) - } - - onAboutToHide: { - if (needCloseButton) { - PageController.drawerClose() - } } onOpened: { isOpened = true + + if (needCloseButton) { + PageController.drawerOpen() + } } onClosed: { isOpened = false + if (needCloseButton) { + PageController.drawerClose() + } + var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor() if (initialPageNavigationBarColor !== 0xFF1C1D21) { PageController.updateNavigationBarColor(initialPageNavigationBarColor) @@ -79,10 +75,9 @@ Drawer { onPositionChanged: { - if (position < (dragMargin / root.height)) { + if (isOpened && (position <= 0.99 && position >= 0.95)) { mouseArea.canceled() drawer.close() - position = 0 mouseArea.exited() dropArea.exited() } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index bfebbb21..d38cec15 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -27,10 +27,8 @@ PageType { property string defaultContainerName: ContainersModel.defaultContainerName Item { - anchors.top: parent.top - anchors.bottom: buttonBackground.top - anchors.right: parent.right - anchors.left: parent.left + anchors.fill: parent + anchors.bottomMargin: buttonContent.collapsedHeight ConnectButton { anchors.centerIn: parent @@ -41,17 +39,45 @@ PageType { target: PageController function onRestorePageHomeState(isContainerInstalled) { - menu.visible = true + buttonContent.state = "expanded" if (isContainerInstalled) { containersDropDown.menuVisible = true } } + function onForceCloseDrawer() { + buttonContent.state = "collapsed" + } + } + + MouseArea { + id: dragArea + + anchors.fill: buttonBackground + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + + drag.target: buttonContent + drag.axis: Drag.YAxis + drag.maximumY: root.height - buttonContent.collapsedHeight + drag.minimumY: 100 + + onReleased: { + if (buttonContent.state === "collapsed" && buttonContent.y < dragArea.drag.maximumY) { + buttonContent.state = "expanded" + return + } + if (buttonContent.state === "expanded" && buttonContent.y > dragArea.drag.minimumY) { + buttonContent.state = "collapsed" + return + } + } } Rectangle { id: buttonBackground - anchors.fill: buttonContent + anchors { left: buttonContent.left; right: buttonContent.right; top: buttonContent.top } + height: root.height radius: 16 color: root.defaultColor border.color: root.borderColor @@ -69,15 +95,67 @@ PageType { ColumnLayout { id: buttonContent - anchors.right: parent.right - anchors.left: parent.left - anchors.bottom: parent.bottom + + property int collapsedHeight: 0 + property bool expandedVisibility: buttonContent.state === "expanded" || (buttonContent.state === "collapsed" && dragArea.drag.active == true) + property bool collapsedVisibility: (buttonContent.state === "collapsed" && dragArea.drag.active == false) || buttonContent.state === "collapsed" + + Drag.active: dragArea.drag.active + anchors.right: root.right + anchors.left: root.left + y: root.height - buttonContent.height + + Component.onCompleted: { + buttonContent.state = "collapsed" + } + + onImplicitHeightChanged: { + if (buttonContent.state === "collapsed" && collapsedHeight == 0) { + collapsedHeight = implicitHeight + } + } + + onStateChanged: { + if (buttonContent.state === "collapsed") { + var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor() + if (initialPageNavigationBarColor !== 0xFF1C1D21) { + PageController.updateNavigationBarColor(initialPageNavigationBarColor) + } + PageController.drawerClose() + return + } + if (buttonContent.state === "expanded") { + if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) { + PageController.updateNavigationBarColor(0xFF1C1D21) + } + PageController.drawerOpen() + return + } + } + + states: [ + State { + name: "collapsed" + PropertyChanges { + target: buttonContent + y: root.height - collapsedHeight + } + }, + State { + name: "expanded" + PropertyChanges { + target: buttonContent + y: dragArea.drag.minimumY + + } + }] RowLayout { Layout.topMargin: 24 Layout.leftMargin: 24 Layout.rightMargin: 24 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + visible: buttonContent.collapsedVisibility spacing: 0 @@ -104,6 +182,7 @@ PageType { LabelTextType { Layout.bottomMargin: 44 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + visible: buttonContent.collapsedVisibility text: { var description = "" @@ -122,39 +201,13 @@ PageType { return description } } - } - - MouseArea { - anchors.fill: buttonBackground - cursorShape: Qt.PointingHandCursor - hoverEnabled: true - - onClicked: { - menu.visible = true - } - } - - DrawerType { - id: menu - - interactive: { - if (stackView && stackView.currentItem) { - return (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) ? true : false - } else { - return false - } - } - dragMargin: buttonBackground.height + 56 // page start tabBar height - - width: parent.width - height: parent.height * 0.9 - pageHeight: root.height ColumnLayout { id: serversMenuHeader - anchors.top: parent.top - anchors.right: parent.right - anchors.left: parent.left + + Layout.alignment: Qt.AlignTop | Qt.AlignHCenter + Layout.fillWidth: true + visible: buttonContent.expandedVisibility Header1TextType { Layout.fillWidth: true @@ -233,13 +286,14 @@ PageType { Layout.topMargin: 48 Layout.leftMargin: 16 Layout.rightMargin: 16 + visible: buttonContent.expandedVisibility actionButtonImage: "qrc:/images/controls/plus.svg" headerText: qsTr("Servers") actionButtonFunction: function() { - menu.visible = false + buttonContent.state = "collapsed" connectionTypeSelection.visible = true } } @@ -249,10 +303,23 @@ PageType { } } - FlickableType { - anchors.top: serversMenuHeader.bottom - anchors.topMargin: 16 + Flickable { + id: serversContainer + Layout.alignment: Qt.AlignTop | Qt.AlignHCenter + Layout.fillWidth: true + Layout.topMargin: 16 contentHeight: col.implicitHeight + height: 500 + visible: buttonContent.expandedVisibility + clip: true + + ScrollBar.vertical: ScrollBar { + id: scrollBar + policy: serversContainer.height >= serversContainer.contentHeight ? ScrollBar.AlwaysOff : ScrollBar.AlwaysOn + } + + Keys.onUpPressed: scrollBar.decrease() + Keys.onDownPressed: scrollBar.increase() Column { id: col @@ -266,6 +333,7 @@ PageType { id: serversRadioButtonGroup } + ListView { id: serversMenuContent width: parent.width @@ -347,7 +415,7 @@ PageType { onClicked: function() { ServersModel.currentlyProcessedIndex = index PageController.goToPage(PageEnum.PageSettingsServerInfo) - menu.visible = false + buttonContent.state = "collapsed" } } } From a6134ca10f0611fdb7c2522b24c589231cf79931 Mon Sep 17 00:00:00 2001 From: Matthew Schwiebert Date: Sun, 1 Oct 2023 20:43:39 -0400 Subject: [PATCH 160/278] fix visibility bug of collapsed state --- client/ui/qml/Pages2/PageHome.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index d38cec15..61fff248 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -97,8 +97,8 @@ PageType { id: buttonContent property int collapsedHeight: 0 - property bool expandedVisibility: buttonContent.state === "expanded" || (buttonContent.state === "collapsed" && dragArea.drag.active == true) - property bool collapsedVisibility: (buttonContent.state === "collapsed" && dragArea.drag.active == false) || buttonContent.state === "collapsed" + property bool expandedVisibility: buttonContent.state === "expanded" || (buttonContent.state === "collapsed" && dragArea.drag.active === true) + property bool collapsedVisibility: buttonContent.state === "collapsed" && dragArea.drag.active === false Drag.active: dragArea.drag.active anchors.right: root.right From 4e6c1094f3aa275ae3127b492490baf659ed4daa Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 2 Oct 2023 16:31:50 +0500 Subject: [PATCH 161/278] minor ui fixes --- .../ui/qml/Components/HomeRootMenuButton.qml | 31 ++++++------------- client/ui/qml/Controls2/BackButtonType.qml | 3 ++ client/ui/qml/Controls2/DropDownType.qml | 17 ++++++++++ client/ui/qml/Controls2/Header2Type.qml | 3 ++ client/ui/qml/Controls2/HeaderType.qml | 3 ++ client/ui/qml/Controls2/ImageButtonType.qml | 11 +++---- .../ui/qml/Controls2/LabelWithButtonType.qml | 3 ++ .../qml/Controls2/TextFieldWithHeaderType.qml | 2 +- .../ui/qml/Controls2/TopCloseButtonType.qml | 3 ++ client/ui/qml/Pages2/PageHome.qml | 6 +++- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 4 +-- 11 files changed, 53 insertions(+), 33 deletions(-) diff --git a/client/ui/qml/Components/HomeRootMenuButton.qml b/client/ui/qml/Components/HomeRootMenuButton.qml index aa6d8f9b..ca800125 100644 --- a/client/ui/qml/Components/HomeRootMenuButton.qml +++ b/client/ui/qml/Components/HomeRootMenuButton.qml @@ -62,25 +62,13 @@ Item { hoverEnabled: false image: rightImageSource imageColor: rightImageColor - visible: rightImageSource ? true : false - implicitSize: 18 - backGroudRadius: 5 - horizontalPadding: 0 - padding: 0 - spacing: 0 - - - Rectangle { - id: rightImageBackground - anchors.fill: parent - radius: 16 - color: "transparent" - - Behavior on color { - PropertyAnimation { duration: 200 } - } - } + icon.width: 18 + icon.height: 18 + backgroundRadius: 16 + horizontalPadding: 4 + topPadding: 4 + bottomPadding: 3 } } @@ -107,26 +95,25 @@ Item { } } - MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor hoverEnabled: true onEntered: { - rightImageBackground.color = rightImage.hoveredColor + rightImage.backgroundColor = rightImage.hoveredColor root.textOpacity = 0.8 } onExited: { - rightImageBackground.color = rightImage.defaultColor + rightImage.backgroundColor = rightImage.defaultColor root.textOpacity = 1 } onPressedChanged: { - rightImageBackground.color = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor + rightImage.backgroundColor = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor root.textOpacity = 0.7 } diff --git a/client/ui/qml/Controls2/BackButtonType.qml b/client/ui/qml/Controls2/BackButtonType.qml index 67ffbd9c..f1044745 100644 --- a/client/ui/qml/Controls2/BackButtonType.qml +++ b/client/ui/qml/Controls2/BackButtonType.qml @@ -23,6 +23,9 @@ Item { image: backButtonImage imageColor: "#D7D8DB" + implicitWidth: 40 + implicitHeight: 40 + onClicked: { if (backButtonFunction && typeof backButtonFunction === "function") { backButtonFunction() diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 2feb5e17..b91f0b7a 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -24,6 +24,8 @@ Item { property string rootButtonImage: "qrc:/images/controls/chevron-down.svg" property string rootButtonImageColor: "#D7D8DB" property string rootButtonBackgroundColor: "#1C1D21" + property string rootButtonBackgroundHoveredColor: "#1C1D21" + property string rootButtonBackgroundPressedColor: "#1C1D21" property string rootButtonHoveredBorderColor: "#494B50" property string rootButtonDefaultBorderColor: "#2C2D30" @@ -71,6 +73,10 @@ Item { Behavior on border.color { PropertyAnimation { duration: 200 } } + + Behavior on color { + PropertyAnimation { duration: 200 } + } } RowLayout { @@ -112,6 +118,9 @@ Item { ImageButtonType { Layout.rightMargin: 16 + implicitWidth: 40 + implicitHeight: 40 + hoverEnabled: false image: rootButtonImage imageColor: rootButtonImageColor @@ -126,12 +135,20 @@ Item { onEntered: { if (menu.visible === false) { rootButtonBackground.border.color = rootButtonHoveredBorderColor + rootButtonBackground.color = rootButtonBackgroundHoveredColor } } onExited: { if (menu.visible === false) { rootButtonBackground.border.color = rootButtonDefaultBorderColor + rootButtonBackground.color = rootButtonBackgroundColor + } + } + + onPressed: { + if (menu.visible === false) { + rootButtonBackground.color = pressed ? rootButtonBackgroundPressedColor : entered ? rootButtonHoveredBorderColor : rootButtonDefaultBorderColor } } diff --git a/client/ui/qml/Controls2/Header2Type.qml b/client/ui/qml/Controls2/Header2Type.qml index 9433f52a..4d812f6c 100644 --- a/client/ui/qml/Controls2/Header2Type.qml +++ b/client/ui/qml/Controls2/Header2Type.qml @@ -31,6 +31,9 @@ Item { ImageButtonType { id: headerActionButton + implicitWidth: 40 + implicitHeight: 40 + image: root.actionButtonImage imageColor: "#D7D8DB" diff --git a/client/ui/qml/Controls2/HeaderType.qml b/client/ui/qml/Controls2/HeaderType.qml index b4af3784..d0ed3418 100644 --- a/client/ui/qml/Controls2/HeaderType.qml +++ b/client/ui/qml/Controls2/HeaderType.qml @@ -36,6 +36,9 @@ Item { ImageButtonType { id: headerActionButton + implicitWidth: 40 + implicitHeight: 40 + Layout.alignment: Qt.AlignRight image: root.actionButtonImage diff --git a/client/ui/qml/Controls2/ImageButtonType.qml b/client/ui/qml/Controls2/ImageButtonType.qml index 55e19f42..1ab57511 100644 --- a/client/ui/qml/Controls2/ImageButtonType.qml +++ b/client/ui/qml/Controls2/ImageButtonType.qml @@ -15,11 +15,8 @@ Button { property string imageColor: "#878B91" property string disableImageColor: "#2C2D30" - property int backGroudRadius: 12 - property int implicitSize: 40 - - implicitWidth: implicitSize - implicitHeight: implicitSize + property alias backgroundColor: background.color + property alias backgroundRadius: background.radius hoverEnabled: true @@ -34,16 +31,16 @@ Button { id: background anchors.fill: parent - radius: backGroudRadius color: { if (root.enabled) { - if(root.pressed) { + if (root.pressed) { return pressedColor } return hovered ? hoveredColor : defaultColor } return defaultColor } + radius: 12 Behavior on color { PropertyAnimation { duration: 200 } } diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml index 7a1489c0..bb051f76 100644 --- a/client/ui/qml/Controls2/LabelWithButtonType.qml +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -119,6 +119,9 @@ Item { ImageButtonType { id: rightImage + implicitWidth: 40 + implicitHeight: 40 + hoverEnabled: false image: rightImageSource imageColor: rightImageColor diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml index 3a3f5a1a..b09ae00d 100644 --- a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -167,7 +167,7 @@ Item { } onEntered: { - backgroud.border.color = getBackgroundBorderColor(bgBorderHoveredColor) + backgroud.border.color = getBackgroundBorderColor(bgBorderHoveredColor) } diff --git a/client/ui/qml/Controls2/TopCloseButtonType.qml b/client/ui/qml/Controls2/TopCloseButtonType.qml index ed89b5a6..4a738214 100644 --- a/client/ui/qml/Controls2/TopCloseButtonType.qml +++ b/client/ui/qml/Controls2/TopCloseButtonType.qml @@ -23,6 +23,9 @@ Popup { image: "qrc:/images/svg/close_black_24dp.svg" imageColor: "#D7D8DB" + implicitWidth: 40 + implicitHeight: 40 + onClicked: { PageController.goToDrawerRootPage() } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 77c60684..ee7cc678 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -134,8 +134,10 @@ PageType { rootButtonImageColor: "#0E0E11" rootButtonBackgroundColor: "#D7D8DB" + rootButtonBackgroundHoveredColor: Qt.rgba(215, 216, 219, 0.8) + rootButtonBackgroundPressedColor: Qt.rgba(215, 216, 219, 0.65) rootButtonHoveredBorderColor: "transparent" - rootButtonPressedBorderColor: "transparent" + rootButtonDefaultBorderColor: "transparent" rootButtonTextTopMargin: 8 rootButtonTextBottomMargin: 8 @@ -304,6 +306,8 @@ PageType { DividerType { Layout.fillWidth: true + Layout.leftMargin: 0 + Layout.rightMargin: 0 } } } diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index 4f94e985..04814c5c 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -62,6 +62,7 @@ PageType { id: header implicitWidth: parent.width + headerTextMaximumLineCount: 10 headerText: qsTr("What is the level of internet control in your region?") } @@ -145,14 +146,13 @@ PageType { Item { implicitWidth: 1 - implicitHeight: 1 + implicitHeight: 54 } BasicButtonType { id: continueButton implicitWidth: parent.width - anchors.topMargin: 24 text: qsTr("Continue") From 50b8b3d649714a3465fbebaa0aa26543fa0b3ad1 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 2 Oct 2023 18:30:32 +0500 Subject: [PATCH 162/278] added parsing of wireguard config parameters when importing native configs --- client/ui/controllers/importController.cpp | 68 +++++++++++++++++++--- 1 file changed, 61 insertions(+), 7 deletions(-) diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index d9278ece..f9cc2d03 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -223,21 +223,75 @@ QJsonObject ImportController::extractOpenVpnConfig(const QString &data) QJsonObject ImportController::extractWireGuardConfig(const QString &data) { + QMap configMap; + auto configByLines = data.split("\n"); + for (const QString &line : configByLines) { + QString trimmedLine = line.trimmed(); + if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) { + continue; + } else { + QStringList parts = trimmedLine.split(" = "); + if (parts.count() == 2) { + configMap[parts.at(0).trimmed()] = parts.at(1).trimmed(); + } + } + } + QJsonObject lastConfig; lastConfig[config_key::config] = data; - const static QRegularExpression hostNameAndPortRegExp("Endpoint = (.*)(?::([0-9]*))?"); + const static QRegularExpression hostNameAndPortRegExp("Endpoint = (.*):([0-9]*)"); QRegularExpressionMatch hostNameAndPortMatch = hostNameAndPortRegExp.match(data); QString hostName; QString port; if (hostNameAndPortMatch.hasCaptured(1)) { hostName = hostNameAndPortMatch.captured(1); - } /*else { - qDebug() << "send error?" - }*/ + } else { + qDebug() << "Failed to import profile"; + emit importErrorOccurred(errorString(ErrorCode::ImportInvalidConfigError)); + } if (hostNameAndPortMatch.hasCaptured(2)) { port = hostNameAndPortMatch.captured(2); + } else { + port = protocols::wireguard::defaultPort; + } + + lastConfig[config_key::hostName] = hostName; + lastConfig[config_key::port] = port.toInt(); + +// if (!configMap.value("PrivateKey").isEmpty() && !configMap.value("Address").isEmpty() +// && !configMap.value("PresharedKey").isEmpty() && !configMap.value("PublicKey").isEmpty()) { + lastConfig[config_key::client_priv_key] = configMap.value("PrivateKey"); + lastConfig[config_key::client_ip] = configMap.value("Address"); + lastConfig[config_key::psk_key] = configMap.value("PresharedKey"); + lastConfig[config_key::server_pub_key] = configMap.value("PublicKey"); +// } else { +// qDebug() << "Failed to import profile"; +// emit importErrorOccurred(errorString(ErrorCode::ImportInvalidConfigError)); +// return QJsonObject(); +// } + + QString protocolName = "wireguard"; + if (!configMap.value(config_key::junkPacketCount).isEmpty() + && !configMap.value(config_key::junkPacketMinSize).isEmpty() + && !configMap.value(config_key::junkPacketMaxSize).isEmpty() + && !configMap.value(config_key::initPacketJunkSize).isEmpty() + && !configMap.value(config_key::responsePacketJunkSize).isEmpty() + && !configMap.value(config_key::initPacketMagicHeader).isEmpty() + && !configMap.value(config_key::responsePacketMagicHeader).isEmpty() + && !configMap.value(config_key::underloadPacketMagicHeader).isEmpty() + && !configMap.value(config_key::transportPacketMagicHeader).isEmpty()) { + lastConfig[config_key::junkPacketCount] = configMap.value(config_key::junkPacketCount); + lastConfig[config_key::junkPacketMinSize] = configMap.value(config_key::junkPacketMinSize); + lastConfig[config_key::junkPacketMaxSize] = configMap.value(config_key::junkPacketMaxSize); + lastConfig[config_key::initPacketJunkSize] = configMap.value(config_key::initPacketJunkSize); + lastConfig[config_key::responsePacketJunkSize] = configMap.value(config_key::responsePacketJunkSize); + lastConfig[config_key::initPacketMagicHeader] = configMap.value(config_key::initPacketMagicHeader); + lastConfig[config_key::responsePacketMagicHeader] = configMap.value(config_key::responsePacketMagicHeader); + lastConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader); + lastConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader); + protocolName = "amneziawireguard"; } QJsonObject wireguardConfig; @@ -247,15 +301,15 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data) wireguardConfig[config_key::transport_proto] = "udp"; QJsonObject containers; - containers.insert(config_key::container, QJsonValue("amnezia-wireguard")); - containers.insert(config_key::wireguard, QJsonValue(wireguardConfig)); + containers.insert(config_key::container, QJsonValue("amnezia-" + protocolName)); + containers.insert(protocolName, QJsonValue(wireguardConfig)); QJsonArray arr; arr.push_back(containers); QJsonObject config; config[config_key::containers] = arr; - config[config_key::defaultContainer] = "amnezia-wireguard"; + config[config_key::defaultContainer] = "amnezia-" + protocolName; config[config_key::description] = m_settings->nextAvailableServerName(); const static QRegularExpression dnsRegExp( From 304f29bfac020be5600ef1a8c9c2dc59177a4805 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 2 Oct 2023 20:03:01 +0500 Subject: [PATCH 163/278] returned 'address' to awg server config and set it to 10.8.1.1/24 --- client/server_scripts/amnezia_wireguard/configure_container.sh | 2 +- client/server_scripts/amnezia_wireguard/start.sh | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/client/server_scripts/amnezia_wireguard/configure_container.sh b/client/server_scripts/amnezia_wireguard/configure_container.sh index fa7b09f9..6ebebc4a 100644 --- a/client/server_scripts/amnezia_wireguard/configure_container.sh +++ b/client/server_scripts/amnezia_wireguard/configure_container.sh @@ -12,7 +12,7 @@ echo $WIREGUARD_PSK > /opt/amnezia/amneziawireguard/wireguard_psk.key cat > /opt/amnezia/amneziawireguard/wg0.conf < Date: Mon, 2 Oct 2023 18:48:11 +0300 Subject: [PATCH 164/278] added parsing parameters for windows --- client/daemon/daemon.cpp | 6 +++++- client/daemon/interfaceconfig.cpp | 31 +++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/client/daemon/daemon.cpp b/client/daemon/daemon.cpp index 63a5c7f6..b85b2c33 100644 --- a/client/daemon/daemon.cpp +++ b/client/daemon/daemon.cpp @@ -360,7 +360,11 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) { return false; } - if (!obj.value("Jc").isNull()) { + if (!obj.value("Jc").isNull() && !obj.value("Jmin").isNull() + && !obj.value("Jmax").isNull() && !obj.value("S1").isNull() + && !obj.value("S2").isNull() && !obj.value("H1").isNull() + && !obj.value("H2").isNull() && !obj.value("H3").isNull() + && !obj.value("H4").isNull()) { config.m_junkPacketCount = obj.value("Jc").toString(); config.m_junkPacketMinSize = obj.value("Jmin").toString(); config.m_junkPacketMaxSize = obj.value("Jmax").toString(); diff --git a/client/daemon/interfaceconfig.cpp b/client/daemon/interfaceconfig.cpp index 68bebca0..b24a35c7 100644 --- a/client/daemon/interfaceconfig.cpp +++ b/client/daemon/interfaceconfig.cpp @@ -97,6 +97,37 @@ QString InterfaceConfig::toWgConf(const QMap& extra) const { out << "DNS = " << dnsServers.join(", ") << "\n"; } + if (!m_junkPacketCount.isNull()) { + out << "JunkPacketCount = " << m_junkPacketCount << "\n"; + } + if (!m_junkPacketMinSize.isNull()) { + out << "JunkPacketMinSize = " << m_junkPacketMinSize << "\n"; + } + if (!m_junkPacketMaxSize.isNull()) { + out << "JunkPacketMaxSize = " << m_junkPacketMaxSize << "\n"; + } + if (!m_initPacketJunkSize.isNull()) { + out << "InitPacketJunkSize = " << m_initPacketJunkSize << "\n"; + } + if (!m_responsePacketJunkSize.isNull()) { + out << "ResponsePacketJunkSize = " << m_responsePacketJunkSize << "\n"; + } + if (!m_initPacketMagicHeader.isNull()) { + out << "InitPacketMagicHeader = " << m_initPacketMagicHeader << "\n"; + } + if (!m_responsePacketMagicHeader.isNull()) { + out << "ResponsePacketMagicHeader = " << m_responsePacketMagicHeader + << "\n"; + } + if (!m_underloadPacketMagicHeader.isNull()) { + out << "UnderloadPacketMagicHeader = " << m_underloadPacketMagicHeader + << "\n"; + } + if (!m_transportPacketMagicHeader.isNull()) { + out << "TransportPacketMagicHeader = " << m_transportPacketMagicHeader + << "\n"; + } + // If any extra config was provided, append it now. for (const QString& key : extra.keys()) { out << key << " = " << extra[key] << "\n"; From 2664a52007596e2b7151e9d04a2efdbb8cbe1182 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 2 Oct 2023 22:04:18 +0500 Subject: [PATCH 165/278] removed the dropdown with protocols on the share full access page --- client/ui/qml/Pages2/PageShare.qml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index a03b3717..b1f0221b 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -219,13 +219,14 @@ PageType { if (accessTypeSelector.currentIndex !== 0) { shareConnectionDrawer.headerText = qsTr("Accessing ") + serverSelector.text - shareConnectionDrawer.configContentHeaderText = qsTr("File with connection settings to ") + serverSelector.text + shareConnectionDrawer.configContentHeaderText = qsTr("File with accessing settings to ") + serverSelector.text } serverSelector.menuVisible = false } Component.onCompleted: { handler() + serverSelector.severSelectorIndexChanged() } function handler() { @@ -240,6 +241,8 @@ PageType { DropDownType { id: protocolSelector + visible: accessTypeSelector.currentIndex === 0 + Layout.fillWidth: true Layout.topMargin: 16 From 5bfc581ad2ba8bb10070ae769c33e6df0dd2b3fd Mon Sep 17 00:00:00 2001 From: Matthew Schwiebert Date: Mon, 2 Oct 2023 15:40:25 -0400 Subject: [PATCH 166/278] add documentation, remove uneeded changes of drawertype --- client/ui/qml/Controls2/DrawerType.qml | 16 +++++++++------- client/ui/qml/Pages2/PageHome.qml | 6 ++++++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/client/ui/qml/Controls2/DrawerType.qml b/client/ui/qml/Controls2/DrawerType.qml index 4ea24091..c22d00c2 100644 --- a/client/ui/qml/Controls2/DrawerType.qml +++ b/client/ui/qml/Controls2/DrawerType.qml @@ -50,22 +50,24 @@ Drawer { if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) { PageController.updateNavigationBarColor(0xFF1C1D21) } - } - - onOpened: { - isOpened = true if (needCloseButton) { PageController.drawerOpen() } } - onClosed: { - isOpened = false - + onAboutToHide: { if (needCloseButton) { PageController.drawerClose() } + } + + onOpened: { + isOpened = true + } + + onClosed: { + isOpened = false var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor() if (initialPageNavigationBarColor !== 0xFF1C1D21) { diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 61fff248..0d39a51e 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -61,6 +61,7 @@ PageType { drag.maximumY: root.height - buttonContent.collapsedHeight drag.minimumY: 100 + /** If drag area is released at any point other than min or max y, transition to the other state */ onReleased: { if (buttonContent.state === "collapsed" && buttonContent.y < dragArea.drag.maximumY) { buttonContent.state = "expanded" @@ -96,8 +97,11 @@ PageType { ColumnLayout { id: buttonContent + /** Initial height of button content */ property int collapsedHeight: 0 + /** True when expanded objects should be visible */ property bool expandedVisibility: buttonContent.state === "expanded" || (buttonContent.state === "collapsed" && dragArea.drag.active === true) + /** True when collapsed objects should be visible */ property bool collapsedVisibility: buttonContent.state === "collapsed" && dragArea.drag.active === false Drag.active: dragArea.drag.active @@ -109,6 +113,7 @@ PageType { buttonContent.state = "collapsed" } + /** Set once based on first implicit height change once all children are layed out */ onImplicitHeightChanged: { if (buttonContent.state === "collapsed" && collapsedHeight == 0) { collapsedHeight = implicitHeight @@ -133,6 +138,7 @@ PageType { } } + /** Two states of buttonContent, great place to add any future animations for the drawer */ states: [ State { name: "collapsed" From 27f770604bb4b48a879668bc89a68dc228dbb5b5 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Tue, 3 Oct 2023 15:59:53 +0800 Subject: [PATCH 167/278] tried to translate some pages, from English to Chinese --- client/translations/amneziavpn_ru.ts | 28 ++-- client/translations/amneziavpn_zh_CN.ts | 132 ++++++++++--------- client/ui/qml/Pages2/PageSettingsLogging.qml | 2 +- 3 files changed, 85 insertions(+), 77 deletions(-) diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index 82393e38..f108cbcd 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -4,7 +4,7 @@ AmneziaApplication - + Split tunneling for WireGuard is not implemented, the option was disabled @@ -26,41 +26,41 @@ ConnectionController - + VPN Protocols is not installed. Please install VPN container at first - + Connection... - + Connected - + Settings updated successfully, Reconnnection... - + Reconnection... - - - - + + + + Connect - + Disconnection... @@ -1120,7 +1120,6 @@ And if you don't like the app, all the more support it - the donation will - Save logs @@ -1129,6 +1128,11 @@ And if you don't like the app, all the more support it - the donation will Open folder with logs + + + Save + + Logs files (*.log) diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index c70b0f75..a1f4c4e9 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -4,7 +4,7 @@ AmneziaApplication - + Split tunneling for WireGuard is not implemented, the option was disabled @@ -26,41 +26,41 @@ ConnectionController - - - - + + + + Connect 连接 - + VPN Protocols is not installed. Please install VPN container at first 未安装VPN协议,请安装 - + Connection... 连接中 - + Connected 已连接 - + Reconnection... 重连中 - + Disconnection... 断开中 - + Settings updated successfully, Reconnnection... 配置已更新,重连中 @@ -477,12 +477,12 @@ Already installed containers were found on the server. All installed containers Continue - + 继续 Cancel - + 取消 @@ -526,12 +526,12 @@ Already installed containers were found on the server. All installed containers Continue - + 继续 Cancel - + 取消 @@ -585,12 +585,12 @@ Already installed containers were found on the server. All installed containers Continue - + 继续 Cancel - + 取消 @@ -673,12 +673,12 @@ Already installed containers were found on the server. All installed containers Continue - + 继续 Cancel - + 取消 @@ -731,12 +731,12 @@ Already installed containers were found on the server. All installed containers Continue - + 继续 Cancel - + 取消 @@ -759,7 +759,7 @@ Already installed containers were found on the server. All installed containers Application - + 应用 @@ -861,77 +861,77 @@ And if you don't like the app, all the more support it - the donation will Application - + 应用 Auto start - + 自动运行 Launch the application every time - + 当系统 starts - + 启动时应用自动运行 Start minimized - + 最小化启动 Launch application minimized - + 最小化启动应用程序 Language - + 语言 Logging - + 日志 Enabled - + 开启 Disabled - + 关闭 Reset settings and remove all data from the application - + 重置并清理应用的所有数据 Reset settings and remove all data from the application? - + 重置并清理应用的所有数据? All settings will be reset to default. All installed AmneziaVPN services will still remain on the server. - + 所有配置恢复为默认值。所有已安装的AmneziaVPN服务将保留服务器上。 Continue - + 继续 Cancel - + 取消 @@ -995,12 +995,12 @@ And if you don't like the app, all the more support it - the donation will Continue - + 继续 Cancel - + 取消 @@ -1081,12 +1081,12 @@ And if you don't like the app, all the more support it - the donation will Continue - + 继续 Cancel - + 取消 @@ -1096,7 +1096,7 @@ And if you don't like the app, all the more support it - the donation will Save - + 保存 @@ -1109,18 +1109,22 @@ And if you don't like the app, all the more support it - the donation will Logging - + 日志 - Save logs - + 记录日志 Open folder with logs - + 打开日志文件夹 + + + + Save + 保存 @@ -1130,32 +1134,32 @@ And if you don't like the app, all the more support it - the donation will Save logs to file - + 保存日志到文件 Clear logs? - + 清除日志? Continue - + 继续 Cancel - + 取消 Logs have been cleaned up - + 已清理日志 Clear logs - + 清理日志 @@ -1195,14 +1199,14 @@ And if you don't like the app, all the more support it - the donation will Continue - + 继续 Cancel - + 取消 @@ -1255,7 +1259,7 @@ And if you don't like the app, all the more support it - the donation will Save - + 保存 @@ -1299,12 +1303,12 @@ And if you don't like the app, all the more support it - the donation will Continue - + 继续 Cancel - + 取消 @@ -1345,12 +1349,12 @@ And if you don't like the app, all the more support it - the donation will Continue - + 继续 Cancel - + 取消 @@ -1481,7 +1485,7 @@ It's okay as long as it's from someone you trust. Continue - + 继续 @@ -1524,7 +1528,7 @@ It's okay as long as it's from someone you trust. Continue - + 继续 @@ -1673,7 +1677,7 @@ It's okay as long as it's from someone you trust. Continue - + 继续 @@ -2421,7 +2425,7 @@ It's okay as long as it's from someone you trust. Choose language - + 选择语言 @@ -2660,7 +2664,7 @@ It's okay as long as it's from someone you trust. Save - + 保存 diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml index 42f33901..4141f51f 100644 --- a/client/ui/qml/Pages2/PageSettingsLogging.qml +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -105,7 +105,7 @@ PageType { if (GC.isMobile()) { fileName = "AmneziaVPN.log" } else { - fileName = SystemController.getFileName(qsTr("Save logs"), + fileName = SystemController.getFileName(qsTr("Save"), qsTr("Logs files (*.log)"), StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN", true, From 617e772cc16ce843fec947b27c5479a717ed8f45 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 3 Oct 2023 18:49:54 +0500 Subject: [PATCH 168/278] added transitions and open/close by clicking on an item for the center menu button --- client/ui/qml/Controls2/DrawerType.qml | 28 ------------- client/ui/qml/Pages2/PageHome.qml | 55 +++++++++++++++++++++----- 2 files changed, 45 insertions(+), 38 deletions(-) diff --git a/client/ui/qml/Controls2/DrawerType.qml b/client/ui/qml/Controls2/DrawerType.qml index 4ea24091..72765d78 100644 --- a/client/ui/qml/Controls2/DrawerType.qml +++ b/client/ui/qml/Controls2/DrawerType.qml @@ -4,7 +4,6 @@ import QtQuick.Controls Drawer { id: drawer property bool needCloseButton: true - property bool isOpened: false Connections { target: PageController @@ -53,16 +52,12 @@ Drawer { } onOpened: { - isOpened = true - if (needCloseButton) { PageController.drawerOpen() } } onClosed: { - isOpened = false - if (needCloseButton) { PageController.drawerClose() } @@ -72,27 +67,4 @@ Drawer { PageController.updateNavigationBarColor(initialPageNavigationBarColor) } } - - - onPositionChanged: { - if (isOpened && (position <= 0.99 && position >= 0.95)) { - mouseArea.canceled() - drawer.close() - mouseArea.exited() - dropArea.exited() - } - } - - DropArea { - id: dropArea - } - - MouseArea { - id: mouseArea - anchors.fill: parent - - onPressed: { - isOpened = true - } - } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 61fff248..8a015e4c 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -26,6 +26,14 @@ PageType { property string defaultServerHostName: ServersModel.defaultServerHostName property string defaultContainerName: ContainersModel.defaultContainerName + MouseArea { + anchors.fill: parent + enabled: buttonContent.state === "expanded" + onClicked: { + buttonContent.state = "collapsed" + } + } + Item { anchors.fill: parent anchors.bottomMargin: buttonContent.collapsedHeight @@ -59,7 +67,7 @@ PageType { drag.target: buttonContent drag.axis: Drag.YAxis drag.maximumY: root.height - buttonContent.collapsedHeight - drag.minimumY: 100 + drag.minimumY: root.height - root.height * 0.9 onReleased: { if (buttonContent.state === "collapsed" && buttonContent.y < dragArea.drag.maximumY) { @@ -71,6 +79,12 @@ PageType { return } } + + onClicked: { + if (buttonContent.state === "collapsed") { + buttonContent.state = "expanded" + } + } } Rectangle { @@ -135,12 +149,12 @@ PageType { states: [ State { - name: "collapsed" - PropertyChanges { - target: buttonContent - y: root.height - collapsedHeight - } - }, + name: "collapsed" + PropertyChanges { + target: buttonContent + y: root.height - collapsedHeight + } + }, State { name: "expanded" PropertyChanges { @@ -148,7 +162,29 @@ PageType { y: dragArea.drag.minimumY } - }] + } + ] + + transitions: [ + Transition { + from: "collapsed" + to: "expanded" + PropertyAnimation { + target: buttonContent + properties: "y" + duration: 200 + } + }, + Transition { + from: "expanded" + to: "collapsed" + PropertyAnimation { + target: buttonContent + properties: "y" + duration: 200 + } + } + ] RowLayout { Layout.topMargin: 24 @@ -309,7 +345,7 @@ PageType { Layout.fillWidth: true Layout.topMargin: 16 contentHeight: col.implicitHeight - height: 500 + implicitHeight: root.height - (root.height * 0.1) - serversMenuHeader.implicitHeight - 52 //todo 52 is tabbar height visible: buttonContent.expandedVisibility clip: true @@ -333,7 +369,6 @@ PageType { id: serversRadioButtonGroup } - ListView { id: serversMenuContent width: parent.width From e2d61cb5188d8526955eb71015b22694cc92c9a3 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 3 Oct 2023 22:38:17 +0500 Subject: [PATCH 169/278] renamed functions and variables --- client/ui/controllers/pageController.cpp | 12 ++++++------ client/ui/controllers/pageController.h | 6 +++--- client/ui/qml/Components/ConnectButton.qml | 2 +- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/pageController.cpp index 2a14548c..cb500618 100644 --- a/client/ui/controllers/pageController.cpp +++ b/client/ui/controllers/pageController.cpp @@ -37,8 +37,8 @@ PageController::PageController(const QSharedPointer &serversModel, connect(this, &PageController::raiseMainWindow, []() { setDockIconVisible(true); }); connect(this, &PageController::hideMainWindow, []() { setDockIconVisible(false); }); #endif - - m_bConnectTrigger = false; + + m_isTriggeredByConnectButton = false; } QString PageController::getInitialPage() @@ -148,12 +148,12 @@ void PageController::drawerClose() } } -bool PageController::isConnectTrigger() +bool PageController::isTriggeredByConnectButton() { - return m_bConnectTrigger; + return m_isTriggeredByConnectButton; } -void PageController::setConnectTrigger(bool trigger) +void PageController::setTriggeredBtConnectButton(bool trigger) { - m_bConnectTrigger = trigger; + m_isTriggeredByConnectButton = trigger; } diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index d16c36a6..70732bd9 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -85,8 +85,8 @@ public slots: void drawerClose(); - bool isConnectTrigger(); - void setConnectTrigger(bool trigger); + bool isTriggeredByConnectButton(); + void setTriggeredBtConnectButton(bool trigger); signals: void goToPage(PageLoader::PageEnum page, bool slide = true); @@ -125,7 +125,7 @@ private: PageLoader::PageEnum m_currentRootPage; int m_drawerLayer; - bool m_bConnectTrigger; + bool m_isTriggeredByConnectButton; }; #endif // PAGECONTROLLER_H diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml index b5c94ea8..76e83da5 100644 --- a/client/ui/qml/Components/ConnectButton.qml +++ b/client/ui/qml/Components/ConnectButton.qml @@ -139,7 +139,7 @@ Button { onClicked: { if (!ContainersModel.isAnyContainerInstalled()) { - PageController.setConnectTrigger(true) + PageController.setTriggeredBtConnectButton(true) ServersModel.currentlyProcessedIndex = ServersModel.getDefaultServerIndex() PageController.goToPage(PageEnum.PageSetupWizardEasy) diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index 08542b23..a5c2eddf 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -184,8 +184,8 @@ PageType { borderWidth: 1 visible: { - if (PageController.isConnectTrigger()) { - PageController.setConnectTrigger(false) + if (PageController.isTriggeredByConnectButton()) { + PageController.setTriggeredBtConnectButton(false) return ContainersModel.isAnyContainerInstalled() } From 7d4a01c757c1b67357b1a0164573ebad8bfd87e2 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 3 Oct 2023 23:28:44 +0500 Subject: [PATCH 170/278] minor ui fixes --- client/ui/controllers/installController.cpp | 3 +-- client/ui/qml/Components/ShareConnectionDrawer.qml | 4 ++-- client/ui/qml/Controls2/BasicButtonType.qml | 3 +++ client/ui/qml/Controls2/TextTypes/ButtonTextType.qml | 2 +- client/ui/qml/Pages2/PageShare.qml | 2 +- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 63510d1a..7e32ff89 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -113,8 +113,7 @@ void InstallController::installServer(DockerContainer container, QJsonObject &co ContainerProps::containerHumanNames().value(container) + tr(" is already installed on the server. "); } if (installedContainers.size() > 1) { - finishMessage += tr("\nAlready installed containers were found on the server. " - "All installed containers have been added to the application"); + finishMessage += tr("\nAdded containers that were already installed on the server"); } if (errorCode == ErrorCode::NoError) { diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 3a8a4175..2419b51a 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -114,7 +114,7 @@ DrawerType { BasicButtonType { Layout.fillWidth: true - Layout.topMargin: 8 + Layout.topMargin: 24 defaultColor: "transparent" hoveredColor: Qt.rgba(1, 1, 1, 0.08) @@ -123,7 +123,7 @@ DrawerType { textColor: "#D7D8DB" borderWidth: 1 - text: qsTr("Show content") + text: qsTr("Show connection settings") onClicked: { configContentDrawer.visible = true diff --git a/client/ui/qml/Controls2/BasicButtonType.qml b/client/ui/qml/Controls2/BasicButtonType.qml index b0c39ddc..a5cde951 100644 --- a/client/ui/qml/Controls2/BasicButtonType.qml +++ b/client/ui/qml/Controls2/BasicButtonType.qml @@ -90,6 +90,9 @@ Button { anchors.centerIn: parent Image { + Layout.preferredHeight: 20 + Layout.preferredWidth: 20 + source: root.imageSource visible: root.imageSource === "" ? false : true diff --git a/client/ui/qml/Controls2/TextTypes/ButtonTextType.qml b/client/ui/qml/Controls2/TextTypes/ButtonTextType.qml index e3b14e63..94b48081 100644 --- a/client/ui/qml/Controls2/TextTypes/ButtonTextType.qml +++ b/client/ui/qml/Controls2/TextTypes/ButtonTextType.qml @@ -6,7 +6,7 @@ Text { color: "#D7D8DB" font.pixelSize: 16 - font.weight: 500 + font.weight: 600 font.family: "PT Root UI VF" wrapMode: Text.WordWrap diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index b1f0221b..00b65310 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -248,7 +248,7 @@ PageType { drawerHeight: 0.5 - descriptionText: qsTr("Protocols") + descriptionText: qsTr("Protocol") headerText: qsTr("Protocol") listView: ListViewWithRadioButtonType { From 30709c66ef397d84342a050cc46f96eb4e215e85 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Wed, 4 Oct 2023 09:05:29 +0800 Subject: [PATCH 171/278] translated settings page --- client/translations/amneziavpn_zh_CN.ts | 44 ++++++++++++------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index a1f4c4e9..08e170b1 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -262,7 +262,7 @@ Already installed containers were found on the server. All installed containers Servers - + 服务器列表 @@ -744,17 +744,17 @@ Already installed containers were found on the server. All installed containers Settings - + 配置列表 Servers - + 服务器列表 Connection - + 连接 @@ -764,12 +764,12 @@ Already installed containers were found on the server. All installed containers Backup - + 备份 About AmneziaVPN - + 关于AmneziaVPN @@ -944,7 +944,7 @@ And if you don't like the app, all the more support it - the donation will Backup - + 备份 @@ -1008,32 +1008,32 @@ And if you don't like the app, all the more support it - the donation will Connection - + 连接 Auto connect - + 自动连接 Connect to VPN on app start - + 应用启动时连接到VPN Use AmneziaDNS if installed on the server - + 使用AmneziaDNS,如果服务器上已安装 DNS servers - + DNS服务器列表 If AmneziaDNS is not used or installed - + 如果AmneziaDNS未使用或安装 @@ -1061,22 +1061,22 @@ And if you don't like the app, all the more support it - the donation will DNS servers - + DNS服务器列表 If AmneziaDNS is not used or installed - + Amnezia DNS未使用或安装 Restore default - + 恢复为默认DNS配置 Restore default DNS settings? - + 是否恢复为默认DNS配置? @@ -1091,7 +1091,7 @@ And if you don't like the app, all the more support it - the donation will Settings have been reset - + 已重配置 @@ -1101,7 +1101,7 @@ And if you don't like the app, all the more support it - the donation will Settings saved - + 配置已保存 @@ -1316,7 +1316,7 @@ And if you don't like the app, all the more support it - the donation will Servers - + 服务器列表 @@ -1743,7 +1743,7 @@ It's okay as long as it's from someone you trust. Connection - + 连接 @@ -1763,7 +1763,7 @@ It's okay as long as it's from someone you trust. Servers - + 服务器列表 From 2353cc4f2c7513d6a9cc62deff68109e629ac1bb Mon Sep 17 00:00:00 2001 From: ronoaer Date: Wed, 4 Oct 2023 15:48:35 +0800 Subject: [PATCH 172/278] translated sub-page of Settings to Chinese --- client/translations/amneziavpn_ru.ts | 19 +-- client/translations/amneziavpn_zh_CN.ts | 118 ++++++++++-------- .../ui/qml/Pages2/PageSettingsConnection.qml | 3 +- 3 files changed, 78 insertions(+), 62 deletions(-) diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index f108cbcd..df1cdf47 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -1029,36 +1029,41 @@ And if you don't like the app, all the more support it - the donation will - Use AmneziaDNS if installed on the server + Use AmneziaDNS - - DNS servers + + If AmneziaDNS is installed on the server + DNS servers + + + + If AmneziaDNS is not used or installed - + Split site tunneling - + Allows you to connect to some sites through a secure connection, and to others bypassing it - + Separate application tunneling - + Allows you to use the VPN only for certain applications diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index 08e170b1..cd46676f 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -6,7 +6,7 @@ Split tunneling for WireGuard is not implemented, the option was disabled - + 禁用选项,还未实现基于WireGuard协议的VPN分流 @@ -37,7 +37,7 @@ VPN Protocols is not installed. Please install VPN container at first - 未安装VPN协议,请安装 + 不存在VPN协议,请先安装 @@ -262,7 +262,7 @@ Already installed containers were found on the server. All installed containers Servers - 服务器列表 + 服务器 @@ -744,12 +744,12 @@ Already installed containers were found on the server. All installed containers Settings - 配置列表 + 设置 Servers - 服务器列表 + 服务器 @@ -769,7 +769,7 @@ Already installed containers were found on the server. All installed containers About AmneziaVPN - 关于AmneziaVPN + 关于 @@ -777,13 +777,14 @@ Already installed containers were found on the server. All installed containers Support the project with a donation - + 捐赠项目 This is a free and open source application. If you like it, support the developers with a donation. And if you don't like the app, all the more support it - the donation will be used to improve the app. - + 这是一个免费且开源的应用程序。如果您喜欢,请捐款支持开发人员。 +如果您不喜欢该应用程序,请更加支持它 - 捐款将用于改进该应用程序。 @@ -803,17 +804,17 @@ And if you don't like the app, all the more support it - the donation will Contacts - + 联系方式 Telegram group - + 电报群 To discuss features - + 用于功能讨论 @@ -823,12 +824,12 @@ And if you don't like the app, all the more support it - the donation will Mail - + 邮件 For reviews and bug reports - + 用于评论和提交软件的缺陷 @@ -843,7 +844,7 @@ And if you don't like the app, all the more support it - the donation will Website - + 官网 @@ -853,7 +854,7 @@ And if you don't like the app, all the more support it - the donation will Check for updates - + 检查更新 @@ -871,22 +872,22 @@ And if you don't like the app, all the more support it - the donation will Launch the application every time - 当系统 + 总是在系统 starts - 启动时应用自动运行 + 启动时自动运行运用程序 Start minimized - 最小化启动 + 最小化 Launch application minimized - 最小化启动应用程序 + 开启应用程序时窗口最小化 @@ -921,7 +922,7 @@ And if you don't like the app, all the more support it - the donation will All settings will be reset to default. All installed AmneziaVPN services will still remain on the server. - 所有配置恢复为默认值。所有已安装的AmneziaVPN服务将保留服务器上。 + 所有配置恢复为默认值。在服务器上保留所有已安装的AmneziaVPN服务。 @@ -939,7 +940,7 @@ And if you don't like the app, all the more support it - the donation will Settings restored from backup file - + 从备份文件还原配置 @@ -949,22 +950,22 @@ And if you don't like the app, all the more support it - the donation will Configuration backup - + 配置备份 It will help you instantly restore connection settings at the next installation - + 帮助您在下次安装时立即恢复连接设置 Make a backup - + 进行备份 Save backup file - + 保存备份 @@ -975,22 +976,22 @@ And if you don't like the app, all the more support it - the donation will Restore from backup - + 从备份还原 Open backup file - + 打开备份文件 Import settings from a backup file? - + 从备份文件导入设置? All current settings will be reset - + 当前所有设置将重置 @@ -1018,42 +1019,51 @@ And if you don't like the app, all the more support it - the donation will Connect to VPN on app start - 应用启动时连接到VPN + 应用开启时连接VPN + + + Use AmneziaDNS if installed on the server + 使用AmneziaDNS,如其已安装在服务器上 - Use AmneziaDNS if installed on the server - 使用AmneziaDNS,如果服务器上已安装 + Use AmneziaDNS + 使用AmneziaDNS - + + If AmneziaDNS is installed on the server + 如其已安装至服务上 + + + DNS servers DNS服务器列表 - + If AmneziaDNS is not used or installed - 如果AmneziaDNS未使用或安装 - - - - Split site tunneling - + 如果未使用或未安装AmneziaDNS - Allows you to connect to some sites through a secure connection, and to others bypassing it - + Split site tunneling + 网站级VPN分流 - - Separate application tunneling - + + Allows you to connect to some sites through a secure connection, and to others bypassing it + 使用VPN访问指定网站,其他的则绕过 + Separate application tunneling + 应用级VPN分流 + + + Allows you to use the VPN only for certain applications - + 仅限指定应用使用VPN @@ -1061,22 +1071,22 @@ And if you don't like the app, all the more support it - the donation will DNS servers - DNS服务器列表 + DNS服务器 If AmneziaDNS is not used or installed - Amnezia DNS未使用或安装 + 如果未使用或未安装Amnezia DNS Restore default - 恢复为默认DNS配置 + 恢复默认配置 Restore default DNS settings? - 是否恢复为默认DNS配置? + 是否恢复默认DNS配置? @@ -1316,7 +1326,7 @@ And if you don't like the app, all the more support it - the donation will Servers - 服务器列表 + 服务器 @@ -1334,7 +1344,7 @@ And if you don't like the app, all the more support it - the donation will Split site tunneling - + 网站级VPN分流 @@ -1629,7 +1639,7 @@ It's okay as long as it's from someone you trust. Settings restored from backup file - + 从备份文件还原配置 @@ -1763,7 +1773,7 @@ It's okay as long as it's from someone you trust. Servers - 服务器列表 + 服务器 diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml index 78d4a681..ae5fd7f4 100644 --- a/client/ui/qml/Pages2/PageSettingsConnection.qml +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -66,7 +66,8 @@ PageType { Layout.fillWidth: true Layout.margins: 16 - text: qsTr("Use AmneziaDNS if installed on the server") + text: qsTr("Use AmneziaDNS") + descriptionText: qsTr("If AmneziaDNS is installed on the server") checked: SettingsController.isAmneziaDnsEnabled() onCheckedChanged: { From a93f75fb5a6defd9a77398324046021b9347ec68 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 4 Oct 2023 14:40:17 +0500 Subject: [PATCH 173/278] added full version to page about --- client/ui/controllers/settingsController.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index 3edfe3d9..78d0dd67 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -22,7 +22,7 @@ SettingsController::SettingsController(const QSharedPointer &serve m_languageModel(languageModel), m_settings(settings) { - m_appVersion = QString("%1: %2 (%3)").arg(tr("Software version"), QString(APP_MAJOR_VERSION), __DATE__); + m_appVersion = QString("%1: %2 (%3)").arg(tr("Software version"), QString(APP_VERSION), __DATE__); #ifdef Q_OS_ANDROID if (!m_settings->isScreenshotsEnabled()) { @@ -193,4 +193,4 @@ void SettingsController::toggleScreenshotsEnabled(bool enable) } }); #endif -} \ No newline at end of file +} From a83ec10b61ae4b3d704f5063e45a4bd1a427d6ac Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 4 Oct 2023 14:47:10 +0500 Subject: [PATCH 174/278] updated description for full access sharing --- client/ui/qml/Pages2/PageShare.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 00b65310..6c284278 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -172,7 +172,7 @@ PageType { Layout.bottomMargin: 24 text: accessTypeSelector.currentIndex === 0 ? qsTr("VPN access without the ability to manage the server") : - qsTr("Full access to server") + qsTr("Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the servers, as well as change settings.") color: "#878B91" } From 6f392ce12632b0c0f18d92be2202cb12b41e1f30 Mon Sep 17 00:00:00 2001 From: pokamest Date: Wed, 4 Oct 2023 16:09:03 +0100 Subject: [PATCH 175/278] Crash on exit fix for Windows --- client/protocols/wireguardprotocol.cpp | 2 -- client/ui/controllers/connectionController.cpp | 17 ----------------- client/ui/controllers/connectionController.h | 2 +- 3 files changed, 1 insertion(+), 20 deletions(-) diff --git a/client/protocols/wireguardprotocol.cpp b/client/protocols/wireguardprotocol.cpp index a4005efb..d675cd02 100644 --- a/client/protocols/wireguardprotocol.cpp +++ b/client/protocols/wireguardprotocol.cpp @@ -101,8 +101,6 @@ void WireguardProtocol::stop() #if defined(Q_OS_MAC) || defined(Q_OS_WIN) || defined(Q_OS_LINUX) ErrorCode WireguardProtocol::startMzImpl() { - - qDebug() << "WireguardProtocol::startMzImpl():" << m_rawConfig; m_impl->activate(m_rawConfig); return ErrorCode::NoError; } diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index 77ca0f5f..9b4ae6d4 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -21,23 +21,6 @@ ConnectionController::ConnectionController(const QSharedPointer &s Qt::QueuedConnection); } -ConnectionController::~ConnectionController() -{ -// todo use ConnectionController instead of using m_vpnConnection directly -#ifdef AMNEZIA_DESKTOP - if (m_vpnConnection->connectionState() != Vpn::ConnectionState::Disconnected) { - m_vpnConnection->disconnectFromVpn(); - for (int i = 0; i < 50; i++) { - qApp->processEvents(QEventLoop::ExcludeUserInputEvents); - QThread::msleep(100); - if (m_vpnConnection->isDisconnected()) { - break; - } - } - } -#endif -} - void ConnectionController::openConnection() { int serverIndex = m_serversModel->getDefaultServerIndex(); diff --git a/client/ui/controllers/connectionController.h b/client/ui/controllers/connectionController.h index 7bfe0fac..e09cdcdf 100644 --- a/client/ui/controllers/connectionController.h +++ b/client/ui/controllers/connectionController.h @@ -19,7 +19,7 @@ public: const QSharedPointer &containersModel, const QSharedPointer &vpnConnection, QObject *parent = nullptr); - ~ConnectionController(); + ~ConnectionController() = default; bool isConnected() const; bool isConnectionInProgress() const; From 9df262d5028c36de1ac4b0af00c02dd7f7021bad Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 4 Oct 2023 19:14:27 +0300 Subject: [PATCH 176/278] fixed sending parameters to the awg daemon for windows --- client/daemon/interfaceconfig.cpp | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/client/daemon/interfaceconfig.cpp b/client/daemon/interfaceconfig.cpp index b24a35c7..8aa06b9b 100644 --- a/client/daemon/interfaceconfig.cpp +++ b/client/daemon/interfaceconfig.cpp @@ -98,34 +98,31 @@ QString InterfaceConfig::toWgConf(const QMap& extra) const { } if (!m_junkPacketCount.isNull()) { - out << "JunkPacketCount = " << m_junkPacketCount << "\n"; + out << "Jc = " << m_junkPacketCount << "\n"; } if (!m_junkPacketMinSize.isNull()) { - out << "JunkPacketMinSize = " << m_junkPacketMinSize << "\n"; + out << "JMin = " << m_junkPacketMinSize << "\n"; } if (!m_junkPacketMaxSize.isNull()) { - out << "JunkPacketMaxSize = " << m_junkPacketMaxSize << "\n"; + out << "JMax = " << m_junkPacketMaxSize << "\n"; } if (!m_initPacketJunkSize.isNull()) { - out << "InitPacketJunkSize = " << m_initPacketJunkSize << "\n"; + out << "S1 = " << m_initPacketJunkSize << "\n"; } if (!m_responsePacketJunkSize.isNull()) { - out << "ResponsePacketJunkSize = " << m_responsePacketJunkSize << "\n"; + out << "S2 = " << m_responsePacketJunkSize << "\n"; } if (!m_initPacketMagicHeader.isNull()) { - out << "InitPacketMagicHeader = " << m_initPacketMagicHeader << "\n"; + out << "H1 = " << m_initPacketMagicHeader << "\n"; } if (!m_responsePacketMagicHeader.isNull()) { - out << "ResponsePacketMagicHeader = " << m_responsePacketMagicHeader - << "\n"; + out << "H2 = " << m_responsePacketMagicHeader << "\n"; } if (!m_underloadPacketMagicHeader.isNull()) { - out << "UnderloadPacketMagicHeader = " << m_underloadPacketMagicHeader - << "\n"; + out << "H3 = " << m_underloadPacketMagicHeader << "\n"; } if (!m_transportPacketMagicHeader.isNull()) { - out << "TransportPacketMagicHeader = " << m_transportPacketMagicHeader - << "\n"; + out << "H4 = " << m_transportPacketMagicHeader << "\n"; } // If any extra config was provided, append it now. From 79d371fb76f94b5ea11e0149b33332ac83abb178 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Thu, 5 Oct 2023 00:54:49 +0800 Subject: [PATCH 177/278] translated pages from english to chinese --- client/translations/amneziavpn_ru.ts | 20 + client/translations/amneziavpn_zh_CN.ts | 581 ++++++++++---------- client/ui/controllers/installController.cpp | 4 +- client/ui/qml/Pages2/PageSettingsDns.qml | 4 +- 4 files changed, 325 insertions(+), 284 deletions(-) diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index df1cdf47..4f397034 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -176,11 +176,21 @@ Already installed containers were found on the server. All installed containers All containers from server ' + + + ' have been removed + + has been removed from the server ' + + + ' + + Please login as the user @@ -1080,6 +1090,16 @@ And if you don't like the app, all the more support it - the donation will If AmneziaDNS is not used or installed + + + Primary DNS + + + + + Secondary DNS + + Restore default diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index cd46676f..a956c4e0 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -20,7 +20,7 @@ VPN Connected Refers to the app - which is currently running the background and waiting - + VPN已连接 @@ -70,17 +70,17 @@ Connection data - + 连接数据 Server IP, login and password - + 服务器IP,用户名和密码 QR code, key or configuration file - + 二维码,授权码或者配置文件 @@ -88,22 +88,22 @@ C&ut - + 剪切 &Copy - + 拷贝 &Paste - + 粘贴 &SelectAll - + 全选 @@ -111,7 +111,7 @@ Access error! - + 访问错误 @@ -119,12 +119,12 @@ The selected protocol is not supported on the current platform - + 当前平台不支持所选协议 Reconnect via VPN Procotol: - + 重连基于VPN协议: @@ -132,7 +132,7 @@ Scanned %1 of %2. - + 扫描 %1 of %2. @@ -141,55 +141,65 @@ installed successfully. - + 安装成功 is already installed on the server. - + 已安装在服务上 Already installed containers were found on the server. All installed containers have been added to the application - + 在服务上发现已经安装协议并添加到应用程序 Settings updated successfully - + 配置更新成功 Server ' - + 服务器 ' was removed - + 已经移除 All containers from server ' + + + ' have been removed + + has been removed from the server ' + 协议已从 + + + + ' Please login as the user - + 请以用户身份登录 Server added successfully - + 服务器添加成功 @@ -197,17 +207,17 @@ Already installed containers were found on the server. All installed containers Read key failed: %1 - + 获取授权码失败: %1 Write key failed: %1 - + 写入授权码失败: %1 Delete key failed: %1 - + 删除授权码失败: %1 @@ -221,22 +231,22 @@ Already installed containers were found on the server. All installed containers VPN Connected - + 已连接到VPN VPN Disconnected - + 已从VPN断开 AmneziaVPN notification - + AmneziaVPN 提示 Unsecured network detected: - + 发现不安全网络 @@ -249,7 +259,7 @@ Already installed containers were found on the server. All installed containers Usually it takes no more than 5 minutes - + 通常5分钟之内完成 @@ -257,7 +267,7 @@ Already installed containers were found on the server. All installed containers VPN protocol - + VPN协议 @@ -270,28 +280,28 @@ Already installed containers were found on the server. All installed containers Cloak settings - + Cloak 配置 Disguised as traffic from - + 伪装流量来自 Port - + 端口 Cipher - + 解码 Save and Restart Amnezia - + 保存并重启Amnezia @@ -299,27 +309,27 @@ Already installed containers were found on the server. All installed containers OpenVPN settings - + OpenVPN 配置 VPN Addresses Subnet - + VPN子网掩码 Network protocol - + 网络协议 Port - + 端口 Auto-negotiate encryption - + 自动协商加密 @@ -381,7 +391,7 @@ Already installed containers were found on the server. All installed containers Cipher - + 解码 @@ -436,43 +446,43 @@ Already installed containers were found on the server. All installed containers TLS auth - + TLS认证 Block DNS requests outside of VPN - + 阻止VPN外的DNS请求 Additional client configuration commands - + 附加客户端配置命令 Commands: - + 命令: Additional server configuration commands - + 附加服务器端配置命令 Remove OpenVPN - + 移除OpenVPN Remove OpenVpn from server? - + 从服务器移除OpenVPN吗? All users with whom you shared a connection will no longer be able to connect to it - + 与您共享连接的所有用户将无法再连接到此链接 @@ -487,7 +497,7 @@ Already installed containers were found on the server. All installed containers Save and Restart Amnezia - + 保存并重启Amnezia @@ -495,33 +505,33 @@ Already installed containers were found on the server. All installed containers settings - + 配置 Show connection options - + 展示连接选项 Connection options - + 连接选项 Remove - + 移除 from server? - + 从服务器 All users with whom you shared a connection will no longer be able to connect to it - + 与您共享连接的所有用户将无法再连接到此链接 @@ -539,23 +549,23 @@ Already installed containers were found on the server. All installed containers ShadowSocks settings - + ShadowSocks 配置 Port - + 端口 Cipher - + 解码 Save and Restart Amnezia - + 保存并重启Amnezia @@ -564,23 +574,23 @@ Already installed containers were found on the server. All installed containers A DNS service is installed on your server, and it is only accessible via VPN. - + 您的服务器上安装了DNS服务,并且只能通过VPN访问。 The DNS address is the same as the address of your server. You can configure DNS in the settings, under the connections tab. - + DNS地址与您的服务器地址相同。您可以在连接选项卡下的设置中配置 DNS Remove - + 移除 from server? - + 从服务器 @@ -598,17 +608,17 @@ Already installed containers were found on the server. All installed containers Settings updated successfully - + 配置更新成功 SFTP settings - + SFTP 配置 Host - + 主机 @@ -616,59 +626,59 @@ Already installed containers were found on the server. All installed containers Copied - + 拷贝 Port - + 端口 Login - + 用户 Password - + 密码 Mount folder on device - + 在设备上挂载文件夹 In order to mount remote SFTP folder as local drive, perform following steps: <br> - + 要将远程 SFTP 文件夹安装为本地驱动器,请执行以下步骤: <br> <br>1. Install the latest version of - + <br>1. 安装最新版的 <br>2. Install the latest version of - + <br>2. 安装最新版的 Detailed instructions - + 详细说明 Remove SFTP and all data stored there - + 删除SFTP和其存储在这里的所有数据 Remove SFTP and all data stored there? - + 删除SFTP和其存储在这里的所有数据? @@ -686,27 +696,27 @@ Already installed containers were found on the server. All installed containers Settings updated successfully - + 配置更新成功 Tor website settings - + Tor网站配置 Website address - + 网址 Copied - + 拷贝 Use <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor Browser</a> to open this url. - + Use <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor 浏览器</a> to open this url. @@ -721,7 +731,7 @@ Already installed containers were found on the server. All installed containers Remove website - + 移除网站 @@ -1033,7 +1043,7 @@ And if you don't like the app, all the more support it - the donation will If AmneziaDNS is installed on the server - 如其已安装至服务上 + 如其已安装至服务器上 @@ -1078,6 +1088,16 @@ And if you don't like the app, all the more support it - the donation will If AmneziaDNS is not used or installed 如果未使用或未安装Amnezia DNS + + + Primary DNS + 首选 DNS + + + + Secondary DNS + 备用 DNS + Restore default @@ -1101,7 +1121,7 @@ And if you don't like the app, all the more support it - the donation will Settings have been reset - 已重配置 + 已重置 @@ -1177,27 +1197,27 @@ And if you don't like the app, all the more support it - the donation will All installed containers have been added to the application - + 所有已安装的容器已添加到应用程序中 No new installed containers found - + 未找到新安装的容器 Clear Amnezia cache - + 清除 Amnezia 缓存 May be needed when changing other settings - + 更改其他设置时可能需要 Clear cached profiles? - + 清除缓存的配置文件? @@ -1221,42 +1241,42 @@ And if you don't like the app, all the more support it - the donation will Check the server for previously installed Amnezia services - + 检查服务器上是否有以前安装的 Amnezia 服务 Add them to the application if they were not displayed - + 如果未显示,请将它们添加到应用程序中 Remove server from application - + 从应用程序中移除服务器 Remove server? - + 移除服务器? All installed AmneziaVPN services will still remain on the server. - + 所有已安装的 AmneziaVPN 服务仍将保留在服务器上。 Clear server from Amnezia software - + 从Amnezia中清除服务器 Clear server from Amnezia software? - + 从Amnezia中清除服务器? All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted. - + 服务器上的所有容器都将被删除。这意味着配置文件、密钥和证书将被删除。 @@ -1264,7 +1284,7 @@ And if you don't like the app, all the more support it - the donation will Server name - + 服务器名称 @@ -1274,17 +1294,17 @@ And if you don't like the app, all the more support it - the donation will Protocols - + 协议 Services - + 服务 Data - + 数据 @@ -1292,23 +1312,23 @@ And if you don't like the app, all the more support it - the donation will settings - + 配置 Remove - + 移除 from server? - + 从服务器 All users with whom you shared a connection will no longer be able to connect to it - + 与您共享连接的所有用户将无法再连接到此链接 @@ -1334,12 +1354,12 @@ And if you don't like the app, all the more support it - the donation will Only the addresses in the list must be opened via VPN - + 仅列表中的地址须通过VPN访问 Addresses from the list should never be opened via VPN - + 勿通过VPN访问列表中的地址 @@ -1349,12 +1369,12 @@ And if you don't like the app, all the more support it - the donation will Mode - + 方式 Remove - + 移除 @@ -1369,27 +1389,27 @@ And if you don't like the app, all the more support it - the donation will Site or IP - + 网址或IP地址 Import/Export Sites - + 导入/导出网址 Import - + 导入 Save site list - + 保存网址 Save sites - + 保存网址 @@ -1401,23 +1421,23 @@ And if you don't like the app, all the more support it - the donation will Import a list of sites - + 导入网址列表 Replace site list - + 替换网址列表 Open sites file - + 打开网址文件 Add imported sites to existing ones - + 将导入的网址添加到现有网址中 @@ -1425,14 +1445,15 @@ And if you don't like the app, all the more support it - the donation will Server connection - + 服务器连接 Do not use connection code from public sources. It may have been created to intercept your data. It's okay as long as it's from someone you trust. - + 请勿使用公共来源的连接代码。它可能是为了拦截您的数据而创建的。 +最好是来源可信。 @@ -1442,27 +1463,27 @@ It's okay as long as it's from someone you trust. File with connection settings or backup - + 包含连接配置或备份的文件 File with connection settings - + 包含连接配置的文件 Open config file - + 打开配置文件 QR-code - + 二维码 Key as text - + 授权码文本 @@ -1470,12 +1491,12 @@ It's okay as long as it's from someone you trust. Server connection - + 服务器连接 Server IP address [:port] - + 服务器IP [:端口] @@ -1485,12 +1506,12 @@ It's okay as long as it's from someone you trust. Login to connect via SSH - + 用户名 Password / SSH private key - + 密码 或者 私钥 @@ -1500,22 +1521,22 @@ It's okay as long as it's from someone you trust. Ip address cannot be empty - + IP不能为空 Enter the address in the format 255.255.255.255:88 - + 按照这种格式输入 255.255.255.255:88 Login cannot be empty - + 用户名不能为空 Password/private key cannot be empty - + 密码或者私钥不能为空 @@ -1523,17 +1544,17 @@ It's okay as long as it's from someone you trust. What is the level of internet control in your region? - + 您所在地区的互联网控制力度如何? Set up a VPN yourself - + 自己架设VPN I want to choose a VPN protocol - + 我想选择VPN协议 @@ -1543,7 +1564,7 @@ It's okay as long as it's from someone you trust. Set up later - + 稍后设置 @@ -1552,32 +1573,32 @@ It's okay as long as it's from someone you trust. Usually it takes no more than 5 minutes - + 通常不超过5分钟 The server has already been added to the application - + 服务器已添加到应用程序中 Amnesia has detected that your server is currently - + Amnezia 检测到您的服务器当前处于 busy installing other software. Amnesia installation - + 正安装其他软件。Amnezia安装 will pause until the server finishes installing other software - + 将暂停,直到服务器完成安装其他软件。 Installing - + 安装中 @@ -1595,17 +1616,17 @@ It's okay as long as it's from someone you trust. Close - + 关闭 Network protocol - + 网络协议 Port - + 端口 @@ -1618,12 +1639,12 @@ It's okay as long as it's from someone you trust. VPN protocol - + VPN 协议 Choose the one with the highest priority for you. Later, you can install other protocols and additional services, such as DNS proxy and SFTP. - + 选择最适合您的一项。稍后,您可以安装其他协议和附加服务,例如 DNS 代理和 SFTP。 @@ -1631,7 +1652,7 @@ It's okay as long as it's from someone you trust. Point the camera at the QR code and hold for a couple of seconds. - + 将相机对准二维码并按住几秒钟 @@ -1667,22 +1688,22 @@ It's okay as long as it's from someone you trust. Connection key - + 连接授权码 A line that starts with vpn://... - + 以 vpn://... 开始的行 Key - + 授权码 Insert - + 插入 @@ -1695,12 +1716,12 @@ It's okay as long as it's from someone you trust. New connection - + 新连接 Do not use connection code from public sources. It could be created to intercept your data. - + 请勿使用公共来源的连接代码。它可以被创建来拦截您的数据。 @@ -1723,32 +1744,32 @@ It's okay as long as it's from someone you trust. Save OpenVPN config - + 保存OpenVPN配置 Save WireGuard config - + 保存WireGuard配置 For the AmneziaVPN app - + 来自应用AmneziaVPN OpenVpn native format - + OpenVPN原生格式 WireGuard native format - + WireGuard原生格式 VPN Access - + 访问VPN @@ -1758,17 +1779,17 @@ It's okay as long as it's from someone you trust. Full access - + 完全授权 VPN access without the ability to manage the server - + VPN 访问,不能管理服务器 Full access to server - + 完全授权方式访问服务器 @@ -1778,44 +1799,44 @@ It's okay as long as it's from someone you trust. Server - + 服务器 Accessing - + 访问 File with connection settings to - + 连接配置文件到 Protocols - + 协议 Protocol - + 协议 Connection to - + 连接到 Connection format - + 连接方式 Share - + 共享 @@ -1823,7 +1844,7 @@ It's okay as long as it's from someone you trust. Close - + 关闭 @@ -1831,38 +1852,38 @@ It's okay as long as it's from someone you trust. Password entry not found - + 没有密码输入 Could not decrypt data - + 不能加密数据 Unknown error - + 位置错误 Could not open wallet: %1; %2 - + 无法打开钱包: %1; %2 Password not found - + 未发现密码 Could not open keystore - + 无法打开密钥库 Could not remove private key from keystore - + 无法从密钥库中删除私钥 @@ -1870,12 +1891,12 @@ It's okay as long as it's from someone you trust. Unknown error - + 未知错误 Access to keychain denied - + 访问钥匙串被拒绝 @@ -1883,27 +1904,27 @@ It's okay as long as it's from someone you trust. Could not store data in settings: access error - + 无法在配置中存储数据:访问错误 Could not store data in settings: format error - + 无法在陪置中存储数据:格式错误 Could not delete data from settings: access error - + 无法在配置中删除数据:访问错误 Could not delete data from settings: format error - + 无法在配置中删除数据:格式错误 Entry not found - + 未找到条目 @@ -1911,13 +1932,13 @@ It's okay as long as it's from someone you trust. Password entry not found - + 没有密码输入 Could not decrypt data - + 不能加密数据 @@ -1938,12 +1959,12 @@ It's okay as long as it's from someone you trust. Could not open wallet: %1; %2 - + 无法打开钱包: %1; %2 Access to keychain denied - + 访问钥匙串被拒绝 @@ -1969,12 +1990,12 @@ It's okay as long as it's from someone you trust. Password not found - + 未发现密码 Could not open keystore - + 无法打开密钥库 @@ -2023,17 +2044,17 @@ It's okay as long as it's from someone you trust. Could not open wallet: %1; %2 - + 无法打开钱包: %1; %2 Password not found - + 未发现密码 Could not open keystore - + 无法打开密钥库 @@ -2066,107 +2087,107 @@ It's okay as long as it's from someone you trust. Sftp service - + Sftp 服务 No error - + 没有错误 Unknown Error - + 位置错误 Function not implemented - + 功能未实现 Server check failed - + 服务器检测失败 Server port already used. Check for another software - + 检测服务器该端口是否被其他软件被占用 Server error: Docker container missing - + Server error: Docker容器丢失 Server error: Docker failed - + Server error: Docker失败 Installation canceled by user - + 用户取消安装 The user does not have permission to use sudo - + 用户没有root权限 Ssh request was denied - + ssh请求被拒绝 Ssh request was interrupted - + ssh请求中断 Ssh internal error - + ssh内部错误 Invalid private key or invalid passphrase entered - + 输入的私钥或密码无效 The selected private key format is not supported, use openssh ED25519 key types or PEM key types - + 不支持所选私钥格式,请使用 openssh ED25519 密钥类型或 PEM 密钥类型 Timeout connecting to server - + 连接服务器超时 Sftp error: End-of-file encountered - + Sftp错误: 遇到文件结尾 Sftp error: File does not exist - + Sftp错误: 文件不存在 Sftp error: Permission denied - + Sftp错误: 权限受限 Sftp error: Generic failure - + Sftp错误: 一般失败 Sftp error: Garbage received from server - + Sftp错误: 从服务器收到垃圾信息 @@ -2211,67 +2232,67 @@ It's okay as long as it's from someone you trust. Failed to save config to disk - + 配置保存到磁盘失败 OpenVPN config missing - + OpenVPN配置丢失 OpenVPN management server error - + OpenVPN 管理服务器错误 OpenVPN executable missing - + OpenVPN 可执行文件丢失 ShadowSocks (ss-local) executable missing - + ShadowSocks (ss-local) 执行文件丢失 Cloak (ck-client) executable missing - + Cloak (ck-client) 执行文件丢失 Amnezia helper service error - + Amnezia 帮助服务错误 OpenSSL failed - + OpenSSL失败 Can't connect: another VPN connection is active - + 无法连接:另一个VPN连接处于活动状态 Can't setup OpenVPN TAP network adapter - + 无法设置 OpenVPN TAP 网络适配器 VPN pool error: no available addresses - + VPN 池错误:没有可用地址 The config does not contain any containers and credentiaks for connecting to the server - + 该配置不包含任何用于连接到服务器的容器和凭据。 Internal error - + 内部错误 @@ -2292,142 +2313,142 @@ It's okay as long as it's from someone you trust. Sftp file sharing service - + SFTP文件共享服务 OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its own security protocol with SSL/TLS for key exchange. - + OpenVPN 是最流行的 VPN 协议,具有灵活的配置选项。它使用自己的安全协议与 SSL/TLS 进行密钥交换。 ShadowSocks - masks VPN traffic, making it similar to normal web traffic, but is recognised by analysis systems in some highly censored regions. - + ShadowSocks - 混淆 VPN 流量,使其与正常的 Web 流量相似,但在一些审查力度高的地区可以被分析系统识别。 OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against active-probbing detection. Ideal for bypassing blocking in regions with the highest levels of censorship. - + OpenVPN over Cloak - OpenVPN,具有伪装成网络流量的 VPN 和针对主动探测检测的保护.非常适合绕过审查力度特别强的地区的封锁。 WireGuard - New popular VPN protocol with high performance, high speed and low power consumption. Recommended for regions with low levels of censorship. - + WireGuard - 新型流行的VPN协议,具有高性能、高速度和低功耗。建议用于审查力度较低的地区 IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS. - + IKEv2 - 现代稳定协议,比其他协议快一点,在信号丢失后恢复连接。Android 和 iOS最新版原生支持。 Deploy a WordPress site on the Tor network in two clicks. - + 只需点击两次即可在 Tor 网络上部署 WordPress 网站 Replace the current DNS server with your own. This will increase your privacy level. - + 将当前的 DNS 服务器替换为您自己的。这将提高您的隐私级别。 Creates a file vault on your server to securely store and transfer files. - + 在您的服务器上创建文件库以安全地存储和传输文件 OpenVPN container - + OpenVPN容器 Container with OpenVpn and ShadowSocks - + 带有 OpenVpn 和 ShadowSocks 的容器 Container with OpenVpn and ShadowSocks protocols configured with traffic masking by Cloak plugin - + 具有 OpenVpn 和 ShadowSocks 协议的容器,通过 Cloak 插件配置混淆流量 WireGuard container - + WireGuard 容器 IPsec container - + IPsec 容器 DNS Service - + DNS 服务 Sftp file sharing service - is secure FTP service - + Sftp 文件共享服务 - 安全的 FTP 服务 Entry not found - + 未找到记录 Access to keychain denied - + 访问钥匙串被拒绝 No keyring daemon - + 没有密钥环守护进程 Already unlocked - + 已经解锁 No such keyring - + 没有这样的密钥环 Bad arguments - + 错误参数 I/O error - + I/O错误 Cancelled - + 已取消 Keyring already exists - + 密匙环已经存在 No match - + 不匹配 Unknown error - + 未知错误 error 0x%1: %2 - + 错误 0x%1: %2 @@ -2449,7 +2470,7 @@ It's okay as long as it's from someone you trust. Server - + 服务器 @@ -2486,7 +2507,7 @@ It's okay as long as it's from someone you trust. Share - + 共享 @@ -2496,7 +2517,7 @@ It's okay as long as it's from someone you trust. Copied - + 拷贝 @@ -2557,7 +2578,7 @@ It's okay as long as it's from someone you trust. Show - + 显示界面 @@ -2567,17 +2588,17 @@ It's okay as long as it's from someone you trust. Disconnect - + 断开 Visit Website - + 访问官网 Quit - + 退出 @@ -2593,22 +2614,22 @@ It's okay as long as it's from someone you trust. Unknown - + 未知 Disconnected - + 断开连接 Preparing - + 准备中 Connecting... - + 连接中 @@ -2618,17 +2639,17 @@ It's okay as long as it's from someone you trust. Disconnecting... - + 断开中 Reconnecting... - + 重连中 Error - + 错误 @@ -2636,32 +2657,32 @@ It's okay as long as it's from someone you trust. Low - + High - + Medium - + I just want to increase the level of privacy - + 我只是想提高隐私级别 Many foreign websites and VPN providers are blocked - + 许多国外网站和VPN提供商被屏蔽 Some foreign sites are blocked, but VPN providers are not blocked - + 一些国外网站被屏蔽,但VPN提供商未被屏蔽 @@ -2669,7 +2690,7 @@ It's okay as long as it's from someone you trust. Private key passphrase - + 私钥密码 diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 63510d1a..8b77252f 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -288,7 +288,7 @@ void InstallController::removeAllContainers() ErrorCode errorCode = m_containersModel->removeAllContainers(); if (errorCode == ErrorCode::NoError) { - emit removeAllContainersFinished(tr("All containers from server '") + serverName + ("' have been removed")); + emit removeAllContainersFinished(tr("All containers from server '") + serverName + tr("' have been removed")); return; } emit installationErrorOccurred(errorString(errorCode)); @@ -305,7 +305,7 @@ void InstallController::removeCurrentlyProcessedContainer() ErrorCode errorCode = m_containersModel->removeCurrentlyProcessedContainer(); if (errorCode == ErrorCode::NoError) { emit removeCurrentlyProcessedContainerFinished(containerName + tr(" has been removed from the server '") - + serverName + "'"); + + serverName + tr("'")); return; } emit installationErrorOccurred(errorString(errorCode)); diff --git a/client/ui/qml/Pages2/PageSettingsDns.qml b/client/ui/qml/Pages2/PageSettingsDns.qml index 0bc13eae..58ec0783 100644 --- a/client/ui/qml/Pages2/PageSettingsDns.qml +++ b/client/ui/qml/Pages2/PageSettingsDns.qml @@ -53,7 +53,7 @@ PageType { id: primaryDns Layout.fillWidth: true - headerText: "Primary DNS" + headerText: qsTr("Primary DNS") textFieldText: SettingsController.primaryDns textField.validator: RegularExpressionValidator { @@ -65,7 +65,7 @@ PageType { id: secondaryDns Layout.fillWidth: true - headerText: "Secondary DNS" + headerText: qsTr("Secondary DNS") textFieldText: SettingsController.secondaryDns textField.validator: RegularExpressionValidator { From 08defbbbd84f7e5f4fddbbb490841ca5b6a9d69f Mon Sep 17 00:00:00 2001 From: ronoaer Date: Thu, 5 Oct 2023 13:26:11 +0800 Subject: [PATCH 178/278] updated original string format, for adapting multi-language --- client/translations/amneziavpn_ru.ts | 66 +++---- client/translations/amneziavpn_zh_CN.ts | 183 ++++++++++-------- client/ui/controllers/installController.cpp | 18 +- client/ui/controllers/sitesController.cpp | 10 +- client/ui/qml/Pages2/PageDeinstalling.qml | 2 +- client/ui/qml/Pages2/PageProtocolRaw.qml | 2 +- .../ui/qml/Pages2/PageServiceDnsSettings.qml | 2 +- .../qml/Pages2/PageSettingsServerProtocol.qml | 2 +- 8 files changed, 140 insertions(+), 145 deletions(-) diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index 4f397034..000e0907 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -139,65 +139,50 @@ InstallController - - installed successfully. + + %1 installed successfully. - - - is already installed on the server. + + + %1 is already installed on the server. - - + + Already installed containers were found on the server. All installed containers have been added to the application - + Settings updated successfully - - Server ' + + Server '%1' was removed - - ' was removed + + All containers from server '%1' have been removed - - All containers from server ' + + 1% has been removed from the server '%2' - - ' have been removed - - - - - has been removed from the server ' - - - - - ' - - - - + Please login as the user - + Server added successfully @@ -253,7 +238,7 @@ Already installed containers were found on the server. All installed containers PageDeinstalling - Removing services from + Removing services from %1 @@ -519,13 +504,12 @@ Already installed containers were found on the server. All installed containers - Remove - from server? + Remove %1 from server? @@ -590,13 +574,12 @@ Already installed containers were found on the server. All installed containers - Remove - from server? + Remove %1 from server? @@ -1318,13 +1301,12 @@ And if you don't like the app, all the more support it - the donation will - Remove - from server? + Remove %1 from server? @@ -2540,22 +2522,22 @@ It's okay as long as it's from someone you trust. - New site added: + New site added: %1 - Site removed: + Site removed: %1 - Can't open file: + Can't open file: %1 - Failed to parse JSON data from file: + Failed to parse JSON data from file: %1 diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index a956c4e0..71e0169b 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -6,7 +6,7 @@ Split tunneling for WireGuard is not implemented, the option was disabled - 禁用选项,还未实现基于WireGuard协议的VPN分流 + 未启用选项,还未实现基于WireGuard协议的VPN分流 @@ -138,66 +138,72 @@ InstallController - - installed successfully. - 安装成功 + 安装成功 - - is already installed on the server. - 已安装在服务上 + 已安装在服务器上 - - + + + %1 installed successfully. + %1 安装成功。 + + + + + %1 is already installed on the server. + 服务器上已经安装 %1。 + + + + Already installed containers were found on the server. All installed containers have been added to the application - 在服务上发现已经安装协议并添加到应用程序 + +在服务上发现已经安装协议并添加到应用程序 - + Settings updated successfully 配置更新成功 - + + Server '%1' was removed + 已移除服务器 '%1' + + + + All containers from server '%1' have been removed + 服务器 '%1' 的所有容器已移除 + + + + 1% has been removed from the server '%2' + 容器 1% 已从服务器 '%2' 上移除 + + Server ' - 服务器 + 服务器 - ' was removed - 已经移除 + 已经移除 - - All containers from server ' - - - - - ' have been removed - - - - has been removed from the server ' - 协议已从 + 协议已从 - - ' - - - - + Please login as the user 请以用户身份登录 - + Server added successfully 服务器添加成功 @@ -253,8 +259,8 @@ Already installed containers were found on the server. All installed containers PageDeinstalling - Removing services from - + Removing services from %1 + 正从 %1 移除服务 @@ -519,14 +525,17 @@ Already installed containers were found on the server. All installed containers - Remove 移除 + Remove %1 from server? + 从服务器移除 %1 ? + + from server? - 从服务器 + 从服务器 @@ -583,14 +592,17 @@ Already installed containers were found on the server. All installed containers - Remove 移除 + Remove %1 from server? + 从服务器移除 %1 ? + + from server? - 从服务器 + 从服务器 @@ -673,12 +685,12 @@ Already installed containers were found on the server. All installed containers Remove SFTP and all data stored there - 删除SFTP和其存储在这里的所有数据 + 移除SFTP和其本地所有数据 Remove SFTP and all data stored there? - 删除SFTP和其存储在这里的所有数据? + 移除SFTP和其本地所有数据? @@ -864,7 +876,7 @@ And if you don't like the app, all the more support it - the donation will Check for updates - 检查更新 + 更新 @@ -917,7 +929,7 @@ And if you don't like the app, all the more support it - the donation will Disabled - 关闭 + 禁用 @@ -1241,22 +1253,22 @@ And if you don't like the app, all the more support it - the donation will Check the server for previously installed Amnezia services - 检查服务器上是否有以前安装的 Amnezia 服务 + 检查服务器上是否存在 Amnezia 服务 Add them to the application if they were not displayed - 如果未显示,请将它们添加到应用程序中 + 如果存在且未被显示,则添加到应用程序里 Remove server from application - 从应用程序中移除服务器 + 移除本地服务器信息 Remove server? - 移除服务器? + 移除本地服务器信息? @@ -1266,7 +1278,7 @@ And if you don't like the app, all the more support it - the donation will Clear server from Amnezia software - 从Amnezia中清除服务器 + 移除Amnezia中服务器信息 @@ -1316,14 +1328,17 @@ And if you don't like the app, all the more support it - the donation will - Remove 移除 - from server? - 从服务器 + 从服务器 + + + + Remove %1 from server? + 从服务器移除 %1 ? @@ -1374,7 +1389,7 @@ And if you don't like the app, all the more support it - the donation will Remove - 移除 + 移除 @@ -1583,7 +1598,7 @@ It's okay as long as it's from someone you trust. Amnesia has detected that your server is currently - Amnezia 检测到您的服务器当前处于 + Amnezia 检测到您的服务器当前 @@ -1731,7 +1746,7 @@ It's okay as long as it's from someone you trust. Show content - + 展示内容 @@ -1754,7 +1769,7 @@ It's okay as long as it's from someone you trust. For the AmneziaVPN app - 来自应用AmneziaVPN + AmneziaVPN 应用 @@ -1779,17 +1794,17 @@ It's okay as long as it's from someone you trust. Full access - 完全授权 + 完整授权 VPN access without the ability to manage the server - VPN 访问,不能管理服务器 + 无权控制服务器 Full access to server - 完全授权方式访问服务器 + 获得服务器完整授权 @@ -1810,7 +1825,7 @@ It's okay as long as it's from someone you trust. File with connection settings to - 连接配置文件到 + 连接配置文件的内容为: @@ -2000,12 +2015,12 @@ It's okay as long as it's from someone you trust. Could not retrieve private key from keystore - + 无法从密钥存储库中检索私钥 Could not create decryption cipher - + 无法创建解密密码 @@ -2059,27 +2074,27 @@ It's okay as long as it's from someone you trust. Could not create private key generator - + 无法创建私钥生成器 Could not generate new private key - + 无法生成新的私钥 Could not retrieve private key from keystore - + 无法从密钥库检索私钥 Could not create encryption cipher - + 无法创建加密密码 Could not encrypt data - + 无法加密数据 @@ -2328,7 +2343,7 @@ It's okay as long as it's from someone you trust. OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against active-probbing detection. Ideal for bypassing blocking in regions with the highest levels of censorship. - OpenVPN over Cloak - OpenVPN,具有伪装成网络流量的 VPN 和针对主动探测检测的保护.非常适合绕过审查力度特别强的地区的封锁。 + OpenVPN over Cloak - OpenVPN 与 VPN 具有伪装成网络流量和防止主动探测检测的保护。非常适合绕过审查力度特别强的地区的封锁。 @@ -2338,7 +2353,7 @@ It's okay as long as it's from someone you trust. IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS. - IKEv2 - 现代稳定协议,比其他协议快一点,在信号丢失后恢复连接。Android 和 iOS最新版原生支持。 + IKEv2 - 现代稳定协议,相比其他协议较快一些,在信号丢失后恢复连接。Android 和 iOS最新版原生支持。 @@ -2478,22 +2493,22 @@ It's okay as long as it's from someone you trust. Software version - + 软件版本 Backup file is corrupted - + 备份文件已损坏 All settings have been reset to default values - + 所配置恢复为默认值 Cached profiles cleared - + 缓存的配置文件已清除 @@ -2502,7 +2517,7 @@ It's okay as long as it's from someone you trust. Save AmneziaVPN config - + 保存配置 @@ -2512,22 +2527,22 @@ It's okay as long as it's from someone you trust. Copy - + 拷贝 Copied - 拷贝 + 已拷贝 Show content - + 展示内容 To read the QR code in the Amnezia app, select "Add server" → "I have data to connect" → "QR code, key or settings file" - + 要读取 Amnezia 应用程序中的二维码,请选择“添加服务器”→“我有数据要连接”→“二维码、密钥或配置文件” @@ -2539,22 +2554,22 @@ It's okay as long as it's from someone you trust. - New site added: + New site added: %1 - Site removed: + Site removed: %1 - Can't open file: + Can't open file: %1 - Failed to parse JSON data from file: + Failed to parse JSON data from file: %1 @@ -2593,7 +2608,7 @@ It's okay as long as it's from someone you trust. Visit Website - 访问官网 + 官网 @@ -2677,7 +2692,7 @@ It's okay as long as it's from someone you trust. Many foreign websites and VPN providers are blocked - 许多国外网站和VPN提供商被屏蔽 + 大多国外网站和VPN提供商被屏蔽 diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 8b77252f..b03a45d8 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -107,10 +107,9 @@ void InstallController::installServer(DockerContainer container, QJsonObject &co if (!installedContainers.contains(container)) { errorCode = serverController.setupContainer(m_currentlyInstalledServerCredentials, container, config); installedContainers.insert(container, config); - finishMessage = ContainerProps::containerHumanNames().value(container) + tr(" installed successfully. "); + finishMessage = tr("%1 installed successfully. ").arg(ContainerProps::containerHumanNames().value(container)); } else { - finishMessage = - ContainerProps::containerHumanNames().value(container) + tr(" is already installed on the server. "); + finishMessage = tr("%1 is already installed on the server. ").arg(ContainerProps::containerHumanNames().value(container)); } if (installedContainers.size() > 1) { finishMessage += tr("\nAlready installed containers were found on the server. " @@ -160,10 +159,9 @@ void InstallController::installContainer(DockerContainer container, QJsonObject if (!installedContainers.contains(container)) { errorCode = serverController.setupContainer(serverCredentials, container, config); installedContainers.insert(container, config); - finishMessage = ContainerProps::containerHumanNames().value(container) + tr(" installed successfully. "); + finishMessage = tr("%1 installed successfully. ").arg(ContainerProps::containerHumanNames().value(container)); } else { - finishMessage = - ContainerProps::containerHumanNames().value(container) + tr(" is already installed on the server. "); + finishMessage = tr("%1 is already installed on the server. ").arg(ContainerProps::containerHumanNames().value(container)); } bool isInstalledContainerAddedToGui = false; @@ -278,7 +276,7 @@ void InstallController::removeCurrentlyProcessedServer() QString serverName = m_serversModel->data(serverIndex, ServersModel::Roles::NameRole).toString(); m_serversModel->removeServer(); - emit removeCurrentlyProcessedServerFinished(tr("Server '") + serverName + tr("' was removed")); + emit removeCurrentlyProcessedServerFinished(tr("Server '%1' was removed").arg(serverName)); } void InstallController::removeAllContainers() @@ -288,7 +286,7 @@ void InstallController::removeAllContainers() ErrorCode errorCode = m_containersModel->removeAllContainers(); if (errorCode == ErrorCode::NoError) { - emit removeAllContainersFinished(tr("All containers from server '") + serverName + tr("' have been removed")); + emit removeAllContainersFinished(tr("All containers from server '%1' have been removed").arg(serverName)); return; } emit installationErrorOccurred(errorString(errorCode)); @@ -304,8 +302,8 @@ void InstallController::removeCurrentlyProcessedContainer() ErrorCode errorCode = m_containersModel->removeCurrentlyProcessedContainer(); if (errorCode == ErrorCode::NoError) { - emit removeCurrentlyProcessedContainerFinished(containerName + tr(" has been removed from the server '") - + serverName + tr("'")); + + emit removeCurrentlyProcessedContainerFinished(tr("1% has been removed from the server '%2'").arg(containerName).arg(serverName)); return; } emit installationErrorOccurred(errorString(errorCode)); diff --git a/client/ui/controllers/sitesController.cpp b/client/ui/controllers/sitesController.cpp index a27e91d0..4d0391be 100644 --- a/client/ui/controllers/sitesController.cpp +++ b/client/ui/controllers/sitesController.cpp @@ -64,7 +64,7 @@ void SitesController::addSite(QString hostname) QHostInfo::lookupHost(hostname, this, resolveCallback); } - emit finished(tr("New site added: ") + hostname); + emit finished(tr("New site added: %1").arg(hostname)); } void SitesController::removeSite(int index) @@ -77,7 +77,7 @@ void SitesController::removeSite(int index) Q_ARG(QStringList, QStringList() << hostname)); QMetaObject::invokeMethod(m_vpnConnection.get(), "flushDns", Qt::QueuedConnection); - emit finished(tr("Site removed: ") + hostname); + emit finished(tr("Site removed: %1").arg(hostname)); } void SitesController::importSites(const QString &fileName, bool replaceExisting) @@ -85,19 +85,19 @@ void SitesController::importSites(const QString &fileName, bool replaceExisting) QFile file(fileName); if (!file.open(QIODevice::ReadOnly)) { - emit errorOccurred(tr("Can't open file: ") + fileName); + emit errorOccurred(tr("Can't open file: %1").arg(fileName)); return; } QByteArray jsonData = file.readAll(); QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonData); if (jsonDocument.isNull()) { - emit errorOccurred(tr("Failed to parse JSON data from file: ") + fileName); + emit errorOccurred(tr("Failed to parse JSON data from file: %1").arg(fileName)); return; } if (!jsonDocument.isArray()) { - emit errorOccurred(tr("The JSON data is not an array in file: ") + fileName); + emit errorOccurred(tr("The JSON data is not an array in file: ").arg(fileName)); return; } diff --git a/client/ui/qml/Pages2/PageDeinstalling.qml b/client/ui/qml/Pages2/PageDeinstalling.qml index 243b1205..8dffbbce 100644 --- a/client/ui/qml/Pages2/PageDeinstalling.qml +++ b/client/ui/qml/Pages2/PageDeinstalling.qml @@ -59,7 +59,7 @@ PageType { Layout.fillWidth: true Layout.topMargin: 20 - headerText: qsTr("Removing services from ") + name + headerText: qsTr("Removing services from %1").arg(name) } ProgressBarType { diff --git a/client/ui/qml/Pages2/PageProtocolRaw.qml b/client/ui/qml/Pages2/PageProtocolRaw.qml index 8bbfab14..34ca4055 100644 --- a/client/ui/qml/Pages2/PageProtocolRaw.qml +++ b/client/ui/qml/Pages2/PageProtocolRaw.qml @@ -173,7 +173,7 @@ PageType { textColor: "#EB5757" clickedFunction: function() { - questionDrawer.headerText = qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() + qsTr(" from server?") + questionDrawer.headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getCurrentlyProcessedContainerName()) questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it") questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.noButtonText = qsTr("Cancel") diff --git a/client/ui/qml/Pages2/PageServiceDnsSettings.qml b/client/ui/qml/Pages2/PageServiceDnsSettings.qml index 016a7c88..10fe6f56 100644 --- a/client/ui/qml/Pages2/PageServiceDnsSettings.qml +++ b/client/ui/qml/Pages2/PageServiceDnsSettings.qml @@ -63,7 +63,7 @@ PageType { textColor: "#EB5757" clickedFunction: function() { - questionDrawer.headerText = qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() + qsTr(" from server?") + questionDrawer.headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getCurrentlyProcessedContainerName()) questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.noButtonText = qsTr("Cancel") diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml index 14d34590..998948d1 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -113,7 +113,7 @@ PageType { textColor: "#EB5757" clickedFunction: function() { - questionDrawer.headerText = qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() + qsTr(" from server?") + questionDrawer.headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getCurrentlyProcessedContainerName()) questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it") questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.noButtonText = qsTr("Cancel") From 396b7aac18977d04caacc3ff9bd2c67daea72757 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 5 Oct 2023 13:56:00 +0500 Subject: [PATCH 179/278] fixed display of amnezia dns description on main menu --- client/ui/qml/Pages2/PageHome.qml | 92 +++++++++++++++++++------------ 1 file changed, 56 insertions(+), 36 deletions(-) diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index d395cd22..3808fb15 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -26,6 +26,55 @@ PageType { property string defaultServerHostName: ServersModel.defaultServerHostName property string defaultContainerName: ContainersModel.defaultContainerName + Connections { + target: PageController + + function onRestorePageHomeState(isContainerInstalled) { + buttonContent.state = "expanded" + if (isContainerInstalled) { + containersDropDown.menuVisible = true + } + } + function onForceCloseDrawer() { + buttonContent.state = "collapsed" + } + } + + Connections { + target: ServersModel + + function onDefaultServerIndexChanged() { + updateDescriptions() + } + } + + Connections { + target: ContainersModel + + function onDefaultContainerChanged() { + updateDescriptions() + } + } + + function updateDescriptions() { + var description = "" + if (ServersModel.isDefaultServerHasWriteAccess()) { + if (SettingsController.isAmneziaDnsEnabled() + && ContainersModel.isAmneziaDnsContainerInstalled(ServersModel.getDefaultServerIndex())) { + description += "Amnezia DNS | " + } + } else { + if (ServersModel.isDefaultServerConfigContainsAmneziaDns()) { + description += "Amnezia DNS | " + } + } + + collapsedServerMenuDescription.text = description + root.defaultContainerName + " | " + root.defaultServerHostName + expandedServersMenuDescription.text = description + root.defaultServerHostName + } + + Component.onCompleted: updateDescriptions() + MouseArea { anchors.fill: parent enabled: buttonContent.state === "expanded" @@ -43,20 +92,6 @@ PageType { } } - Connections { - target: PageController - - function onRestorePageHomeState(isContainerInstalled) { - buttonContent.state = "expanded" - if (isContainerInstalled) { - containersDropDown.menuVisible = true - } - } - function onForceCloseDrawer() { - buttonContent.state = "collapsed" - } - } - MouseArea { id: dragArea @@ -255,26 +290,10 @@ PageType { } LabelTextType { + id: collapsedServerMenuDescription Layout.bottomMargin: 44 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter visible: buttonContent.collapsedVisibility - - text: { - var description = "" - if (ServersModel.isDefaultServerHasWriteAccess()) { - if (SettingsController.isAmneziaDnsEnabled() - && ContainersModel.isAmneziaDnsContainerInstalled(ServersModel.getDefaultServerIndex())) { - description += "Amnezia DNS | " - } - } else { - if (ServersModel.isDefaultServerConfigContainsAmneziaDns()) { - description += "Amnezia DNS | " - } - } - - description += root.defaultContainerName + " | " + root.defaultServerHostName - return description - } } ColumnLayout { @@ -297,10 +316,11 @@ PageType { } LabelTextType { + id: expandedServersMenuDescription Layout.bottomMargin: 24 - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - - text: root.defaultServerHostName + Layout.fillWidth: true + horizontalAlignment: Qt.AlignHCenter + verticalAlignment: Qt.AlignVCenter } RowLayout { @@ -450,11 +470,11 @@ PageType { if (hasWriteAccess) { if (SettingsController.isAmneziaDnsEnabled() && ContainersModel.isAmneziaDnsContainerInstalled(index)) { - description += "AmneziaDNS | " + description += "Amnezia DNS | " } } else { if (containsAmneziaDns) { - description += "AmneziaDNS | " + description += "Amnezia DNS | " } } From 1eafa9a38a8c0c02d8f85e4c0afdda50d58012b7 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Thu, 5 Oct 2023 23:47:50 +0800 Subject: [PATCH 180/278] updated about and tor --- client/translations/amneziavpn_zh_CN.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index 71e0169b..e7171ce2 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -728,17 +728,17 @@ Already installed containers were found on the server. All installed containers Use <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor Browser</a> to open this url. - Use <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor 浏览器</a> to open this url. + 用 <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor 浏览器</a> 打开上面网址 After installation it takes several minutes while your onion site will become available in the Tor Network. - + 安装几分钟后,洋葱站点才会在 Tor 网络中生效。 When configuring WordPress set the domain as this onion address. - + 配置 WordPress 时,将域设置为此洋葱地址。 @@ -748,7 +748,7 @@ Already installed containers were found on the server. All installed containers The site with all data will be removed from the tor network. - + 网站及其所有数据将从 Tor 网络中删除 @@ -805,13 +805,13 @@ Already installed containers were found on the server. All installed containers This is a free and open source application. If you like it, support the developers with a donation. And if you don't like the app, all the more support it - the donation will be used to improve the app. - 这是一个免费且开源的应用程序。如果您喜欢,请捐款支持开发人员。 -如果您不喜欢该应用程序,请更加支持它 - 捐款将用于改进该应用程序。 + 这是一个免费且开源的应用。如果您喜欢,请捐助支持我们。 +如果您不喜欢该应用,请更加支持它 - 捐款将用于改进该应用。 Card on Patreon - + 请在Patreon上支持我们 @@ -2358,7 +2358,7 @@ It's okay as long as it's from someone you trust. Deploy a WordPress site on the Tor network in two clicks. - 只需点击两次即可在 Tor 网络上部署 WordPress 网站 + 只需点两次即可架设 WordPress 网站到 Tor 网络 @@ -2593,7 +2593,7 @@ It's okay as long as it's from someone you trust. Show - 显示界面 + 界面 From 3a77705142d8ee99c73f3269986d27bda9499ed9 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Thu, 5 Oct 2023 15:55:32 -0400 Subject: [PATCH 181/278] Update AWG binary --- client/3rd-prebuilt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/3rd-prebuilt b/client/3rd-prebuilt index 6f0d654a..c6d77cff 160000 --- a/client/3rd-prebuilt +++ b/client/3rd-prebuilt @@ -1 +1 @@ -Subproject commit 6f0d654a2409e2f634e7f7b95d34998c8eba2d7b +Subproject commit c6d77cff35bcdef34306ab5ef594a313726949da From 08863edb520d99ac2931510469302ca1d755f730 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Thu, 5 Oct 2023 17:11:40 -0400 Subject: [PATCH 182/278] Update AWG iOS binary again --- client/3rd-prebuilt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/3rd-prebuilt b/client/3rd-prebuilt index c6d77cff..994f4f2b 160000 --- a/client/3rd-prebuilt +++ b/client/3rd-prebuilt @@ -1 +1 @@ -Subproject commit c6d77cff35bcdef34306ab5ef594a313726949da +Subproject commit 994f4f2b030600f2d8dbf9dccf409b1591c9e463 From d77be5a244252849720763ff7a2c7e672f53b520 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Fri, 6 Oct 2023 00:38:54 +0300 Subject: [PATCH 183/278] Update iOS network extension --- client/3rd/awg-apple | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/3rd/awg-apple b/client/3rd/awg-apple index 5767a03f..fab07138 160000 --- a/client/3rd/awg-apple +++ b/client/3rd/awg-apple @@ -1 +1 @@ -Subproject commit 5767a03f75a2b77d4f78fdd77ff51a1eefabe3b0 +Subproject commit fab07138dbab06ac0de256021e47e273f4df8e88 From 1357c4a3096f7c142d505cad48b01e31ef2c52e7 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Fri, 6 Oct 2023 13:43:32 +0800 Subject: [PATCH 184/278] applied translation-funcation to SystemTray --- client/amnezia_application.cpp | 1 + client/ui/notificationhandler.cpp | 4 ++++ client/ui/notificationhandler.h | 1 + client/ui/systemtray_notificationhandler.cpp | 17 ++++++++++++----- client/ui/systemtray_notificationhandler.h | 6 +++++- 5 files changed, 23 insertions(+), 6 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 01b37229..801b90b4 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -139,6 +139,7 @@ void AmneziaApplication::init() &ConnectionController::openConnection); connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(), &ConnectionController::closeConnection); + connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated); m_engine->load(url); m_systemController->setQmlRoot(m_engine->rootObjects().value(0)); diff --git a/client/ui/notificationhandler.cpp b/client/ui/notificationhandler.cpp index f932eb17..1f81c2c2 100644 --- a/client/ui/notificationhandler.cpp +++ b/client/ui/notificationhandler.cpp @@ -88,6 +88,10 @@ void NotificationHandler::setConnectionState(Vpn::ConnectionState state) } } +void NotificationHandler::onTranslationsUpdated() +{ +} + void NotificationHandler::unsecuredNetworkNotification(const QString& networkName) { qDebug() << "Unsecured network notification shown"; diff --git a/client/ui/notificationhandler.h b/client/ui/notificationhandler.h index 9a2182de..abdce27b 100644 --- a/client/ui/notificationhandler.h +++ b/client/ui/notificationhandler.h @@ -32,6 +32,7 @@ public: public slots: virtual void setConnectionState(Vpn::ConnectionState state); + virtual void onTranslationsUpdated(); signals: void notificationShown(const QString& title, const QString& message); diff --git a/client/ui/systemtray_notificationhandler.cpp b/client/ui/systemtray_notificationhandler.cpp index 6adc8818..2c7c695f 100644 --- a/client/ui/systemtray_notificationhandler.cpp +++ b/client/ui/systemtray_notificationhandler.cpp @@ -17,7 +17,6 @@ #include "version.h" - SystemTrayNotificationHandler::SystemTrayNotificationHandler(QObject* parent) : NotificationHandler(parent), m_systemTrayIcon(parent) @@ -26,8 +25,7 @@ SystemTrayNotificationHandler::SystemTrayNotificationHandler(QObject* parent) : m_systemTrayIcon.show(); connect(&m_systemTrayIcon, &QSystemTrayIcon::activated, this, &SystemTrayNotificationHandler::onTrayActivated); - - m_menu.addAction(QIcon(":/images/tray/application.png"), tr("Show") + " " + APPLICATION_NAME, this, [this](){ + m_trayActionShow = m_menu.addAction(QIcon(":/images/tray/application.png"), tr("Show") + " " + APPLICATION_NAME, this, [this](){ emit raiseRequested(); }); m_menu.addSeparator(); @@ -36,11 +34,11 @@ SystemTrayNotificationHandler::SystemTrayNotificationHandler(QObject* parent) : m_menu.addSeparator(); - m_menu.addAction(QIcon(":/images/tray/link.png"), tr("Visit Website"), [&](){ + m_trayActionVisitWebSite = m_menu.addAction(QIcon(":/images/tray/link.png"), tr("Visit Website"), [&](){ QDesktopServices::openUrl(QUrl("https://amnezia.org")); }); - m_menu.addAction(QIcon(":/images/tray/cancel.png"), tr("Quit") + " " + APPLICATION_NAME, this, [&](){ + m_trayActionQuit = m_menu.addAction(QIcon(":/images/tray/cancel.png"), tr("Quit") + " " + APPLICATION_NAME, this, [&](){ qApp->quit(); }); @@ -57,6 +55,15 @@ void SystemTrayNotificationHandler::setConnectionState(Vpn::ConnectionState stat NotificationHandler::setConnectionState(state); } +void SystemTrayNotificationHandler::onTranslationsUpdated() +{ + m_trayActionShow->setText(tr("Show") + " " + APPLICATION_NAME); + m_trayActionConnect->setText(tr("Connect")); + m_trayActionDisconnect->setText(tr("Disconnect")); + m_trayActionVisitWebSite->setText(tr("Visit Website")); + m_trayActionQuit->setText(tr("Quit")+ " " + APPLICATION_NAME); +} + void SystemTrayNotificationHandler::setTrayIcon(const QString &iconPath) { QIcon trayIconMask(QPixmap(iconPath).scaled(128,128)); diff --git a/client/ui/systemtray_notificationhandler.h b/client/ui/systemtray_notificationhandler.h index 96134f14..60bf0b35 100644 --- a/client/ui/systemtray_notificationhandler.h +++ b/client/ui/systemtray_notificationhandler.h @@ -19,6 +19,8 @@ public: void setConnectionState(Vpn::ConnectionState state) override; + void onTranslationsUpdated() override; + protected: virtual void notify(Message type, const QString& title, const QString& message, int timerMsec) override; @@ -35,9 +37,11 @@ private: QMenu m_menu; QSystemTrayIcon m_systemTrayIcon; + QAction* m_trayActionShow = nullptr; QAction* m_trayActionConnect = nullptr; QAction* m_trayActionDisconnect = nullptr; - QAction* m_preferencesAction = nullptr; + QAction* m_trayActionVisitWebSite = nullptr; + QAction* m_trayActionQuit = nullptr; QAction* m_statusLabel = nullptr; QAction* m_separator = nullptr; From 9377a0b545740cd0133c5a74b2d255caf6fc37a4 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Fri, 6 Oct 2023 14:37:10 +0800 Subject: [PATCH 185/278] updated translated-text to connectStatusText in ConnectionController --- client/amnezia_application.cpp | 2 ++ client/ui/controllers/connectionController.cpp | 3 ++- client/ui/controllers/connectionController.h | 2 +- client/ui/qml/Pages2/PageStart.qml | 2 -- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 801b90b4..d8039d9b 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -330,6 +330,8 @@ void AmneziaApplication::initControllers() m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel, m_vpnConnection)); m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get()); + connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(), &ConnectionController::onTranslationsUpdated); + m_pageController.reset(new PageController(m_serversModel, m_settings)); m_engine->rootContext()->setContextProperty("PageController", m_pageController.get()); diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index 8df62a94..34ac1399 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -130,8 +130,9 @@ void ConnectionController::onCurrentContainerUpdated() } } -void ConnectionController::translateMemoryText() +void ConnectionController::onTranslationsUpdated() { + // get translated text of current state onConnectionStateChanged(getCurrentConnectionState()); } diff --git a/client/ui/controllers/connectionController.h b/client/ui/controllers/connectionController.h index c1eaf38f..9d3082a2 100644 --- a/client/ui/controllers/connectionController.h +++ b/client/ui/controllers/connectionController.h @@ -34,7 +34,7 @@ public slots: void onCurrentContainerUpdated(); - void translateMemoryText(); + void onTranslationsUpdated(); signals: void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index f36aac41..43366af7 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -171,8 +171,6 @@ PageType { onClicked: { tabBarStackView.goToTabBarPage(PageEnum.PageHome) ServersModel.currentlyProcessedIndex = ServersModel.defaultIndex - - ConnectionController.translateMemoryText() } } TabImageButtonType { From 079c9176ef9afef6dd489be5cdbb6d8cd706fae6 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Fri, 6 Oct 2023 15:29:15 +0800 Subject: [PATCH 186/278] fixed minor issues of translation --- client/translations/amneziavpn_ru.ts | 25 +++++++----- client/translations/amneziavpn_zh_CN.ts | 39 ++++++++++++------- client/ui/controllers/installController.cpp | 2 +- .../PageSetupWizardProtocolSettings.qml | 2 +- 4 files changed, 41 insertions(+), 27 deletions(-) diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index 000e0907..e0bab018 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -4,7 +4,7 @@ AmneziaApplication - + Split tunneling for WireGuard is not implemented, the option was disabled @@ -173,7 +173,7 @@ Already installed containers were found on the server. All installed containers - 1% has been removed from the server '%2' + %1 has been removed from the server '%2' @@ -224,12 +224,12 @@ Already installed containers were found on the server. All installed containers - + AmneziaVPN notification - + Unsecured network detected: @@ -1588,7 +1588,7 @@ It's okay as long as it's from someone you trust. PageSetupWizardProtocolSettings - Installing + Installing %1 @@ -2559,27 +2559,32 @@ It's okay as long as it's from someone you trust. SystemTrayNotificationHandler - + + Show - + + Connect - + + Disconnect - + + Visit Website - + + Quit diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index e7171ce2..b89674e5 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -4,7 +4,7 @@ AmneziaApplication - + Split tunneling for WireGuard is not implemented, the option was disabled 未启用选项,还未实现基于WireGuard协议的VPN分流 @@ -182,8 +182,12 @@ Already installed containers were found on the server. All installed containers + %1 has been removed from the server '%2' + %1 已从服务器 '%2' 上移除 + + 1% has been removed from the server '%2' - 容器 1% 已从服务器 '%2' 上移除 + %1 已从服务器 '%2' 上移除 Server ' @@ -245,12 +249,12 @@ Already installed containers were found on the server. All installed containers 已从VPN断开 - + AmneziaVPN notification AmneziaVPN 提示 - + Unsecured network detected: 发现不安全网络 @@ -1620,13 +1624,13 @@ It's okay as long as it's from someone you trust. PageSetupWizardProtocolSettings - Installing - + Installing %1 + 正在安装 %1 More detailed - + 更多细节 @@ -1646,7 +1650,7 @@ It's okay as long as it's from someone you trust. Install - + 安装 @@ -2318,7 +2322,7 @@ It's okay as long as it's from someone you trust. Website in Tor network - + 在 Tor 网络中架设网站 @@ -2358,7 +2362,7 @@ It's okay as long as it's from someone you trust. Deploy a WordPress site on the Tor network in two clicks. - 只需点两次即可架设 WordPress 网站到 Tor 网络 + 只需点击两次即可架设 WordPress 网站到 Tor 网络 @@ -2591,27 +2595,32 @@ It's okay as long as it's from someone you trust. SystemTrayNotificationHandler - + + Show 界面 - + + Connect 连接 - + + Disconnect 断开 - + + Visit Website 官网 - + + Quit 退出 diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index b03a45d8..c25a3129 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -303,7 +303,7 @@ void InstallController::removeCurrentlyProcessedContainer() ErrorCode errorCode = m_containersModel->removeCurrentlyProcessedContainer(); if (errorCode == ErrorCode::NoError) { - emit removeCurrentlyProcessedContainerFinished(tr("1% has been removed from the server '%2'").arg(containerName).arg(serverName)); + emit removeCurrentlyProcessedContainerFinished(tr("%1 has been removed from the server '%2'").arg(containerName).arg(serverName)); return; } emit installationErrorOccurred(errorString(errorCode)); diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 07eef177..7535464a 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -72,7 +72,7 @@ PageType { Layout.fillWidth: true - headerText: qsTr("Installing ") + name + headerText: qsTr("Installing %1").arg(name) descriptionText: description } From 3fb97d16bbb6566c2a26e2c2c4afc4581a992c9d Mon Sep 17 00:00:00 2001 From: ronoaer Date: Fri, 6 Oct 2023 15:49:48 +0800 Subject: [PATCH 187/278] updated about page --- client/translations/amneziavpn_zh_CN.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index b89674e5..cd82e8f7 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -803,14 +803,14 @@ Already installed containers were found on the server. All installed containers Support the project with a donation - 捐赠项目 + 捐款 This is a free and open source application. If you like it, support the developers with a donation. And if you don't like the app, all the more support it - the donation will be used to improve the app. - 这是一个免费且开源的应用。如果您喜欢,请捐助支持我们。 -如果您不喜欢该应用,请更加支持它 - 捐款将用于改进该应用。 + 这是一个免费且开源的应用软件。如果您喜欢它,请捐助支持我们继续研发。 +如果您不喜欢,请捐助支持我们改进它。 From 673f28ed648677f92a46001ee774bbfce5260ba0 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Fri, 6 Oct 2023 16:32:04 +0800 Subject: [PATCH 188/278] retanslate donation way --- client/translations/amneziavpn_zh_CN.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index cd82e8f7..37e27786 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -815,7 +815,7 @@ And if you don't like the app, all the more support it - the donation will Card on Patreon - 请在Patreon上支持我们 + Patreon订阅 @@ -825,7 +825,7 @@ And if you don't like the app, all the more support it - the donation will Show other methods on Github - + 其他捐款途径 From b7a65343af4373753841b7db3c8f2bd5c4260cc6 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 6 Oct 2023 16:43:52 +0500 Subject: [PATCH 189/278] added the ability to change awg parameters on the protocol settings page --- client/configurators/awg_configurator.cpp | 73 ++++++----- .../configurators/wireguard_configurator.cpp | 8 +- client/configurators/wireguard_configurator.h | 2 + client/core/servercontroller.cpp | 4 + .../protocols/amneziaWireGuardConfigModel.cpp | 81 ++++++++++-- .../protocols/amneziaWireGuardConfigModel.h | 10 +- .../Components/SettingsContainersListView.qml | 2 +- .../qml/Controls2/TextFieldWithHeaderType.qml | 7 ++ .../PageProtocolAmneziaWireGuardSettings.qml | 117 +++++++++++++----- 9 files changed, 224 insertions(+), 80 deletions(-) diff --git a/client/configurators/awg_configurator.cpp b/client/configurators/awg_configurator.cpp index 85dbd6de..6ed1cd1b 100644 --- a/client/configurators/awg_configurator.cpp +++ b/client/configurators/awg_configurator.cpp @@ -3,6 +3,8 @@ #include #include +#include "core/servercontroller.h" + AmneziaWireGuardConfigurator::AmneziaWireGuardConfigurator(std::shared_ptr settings, QObject *parent) : WireguardConfigurator(settings, true, parent) { @@ -15,46 +17,43 @@ QString AmneziaWireGuardConfigurator::genAmneziaWireGuardConfig(const ServerCred QString config = WireguardConfigurator::genWireguardConfig(credentials, container, containerConfig, errorCode); QJsonObject jsonConfig = QJsonDocument::fromJson(config.toUtf8()).object(); - QJsonObject awgConfig = containerConfig.value(config_key::amneziaWireguard).toObject(); - auto junkPacketCount = - awgConfig.value(config_key::junkPacketCount).toString(protocols::amneziawireguard::defaultJunkPacketCount); - auto junkPacketMinSize = - awgConfig.value(config_key::junkPacketMinSize).toString(protocols::amneziawireguard::defaultJunkPacketMinSize); - auto junkPacketMaxSize = - awgConfig.value(config_key::junkPacketMaxSize).toString(protocols::amneziawireguard::defaultJunkPacketMaxSize); - auto initPacketJunkSize = - awgConfig.value(config_key::initPacketJunkSize).toString(protocols::amneziawireguard::defaultInitPacketJunkSize); - auto responsePacketJunkSize = - awgConfig.value(config_key::responsePacketJunkSize).toString(protocols::amneziawireguard::defaultResponsePacketJunkSize); - auto initPacketMagicHeader = - awgConfig.value(config_key::initPacketMagicHeader).toString(protocols::amneziawireguard::defaultInitPacketMagicHeader); - auto responsePacketMagicHeader = - awgConfig.value(config_key::responsePacketMagicHeader).toString(protocols::amneziawireguard::defaultResponsePacketMagicHeader); - auto underloadPacketMagicHeader = - awgConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::amneziawireguard::defaultUnderloadPacketMagicHeader); - auto transportPacketMagicHeader = - awgConfig.value(config_key::transportPacketMagicHeader).toString(protocols::amneziawireguard::defaultTransportPacketMagicHeader); + ServerController serverController(m_settings); + QString serverConfig = serverController.getTextFileFromContainer(container, credentials, protocols::amneziawireguard::serverConfigPath, errorCode); - config.replace("$JUNK_PACKET_COUNT", junkPacketCount); - config.replace("$JUNK_PACKET_MIN_SIZE", junkPacketMinSize); - config.replace("$JUNK_PACKET_MAX_SIZE", junkPacketMaxSize); - config.replace("$INIT_PACKET_JUNK_SIZE", initPacketJunkSize); - config.replace("$RESPONSE_PACKET_JUNK_SIZE", responsePacketJunkSize); - config.replace("$INIT_PACKET_MAGIC_HEADER", initPacketMagicHeader); - config.replace("$RESPONSE_PACKET_MAGIC_HEADER", responsePacketMagicHeader); - config.replace("$UNDERLOAD_PACKET_MAGIC_HEADER", underloadPacketMagicHeader); - config.replace("$TRANSPORT_PACKET_MAGIC_HEADER", transportPacketMagicHeader); + QMap serverConfigMap; + auto serverConfigLines = serverConfig.split("\n"); + for (auto &line : serverConfigLines) { + auto trimmedLine = line.trimmed(); + if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) { + continue; + } else { + QStringList parts = trimmedLine.split(" = "); + if (parts.count() == 2) { + serverConfigMap.insert(parts[0].trimmed(), parts[1].trimmed()); + } + } + } - jsonConfig[config_key::junkPacketCount] = junkPacketCount; - jsonConfig[config_key::junkPacketMinSize] = junkPacketMinSize; - jsonConfig[config_key::junkPacketMaxSize] = junkPacketMaxSize; - jsonConfig[config_key::initPacketJunkSize] = initPacketJunkSize; - jsonConfig[config_key::responsePacketJunkSize] = responsePacketJunkSize; - jsonConfig[config_key::initPacketMagicHeader] = initPacketMagicHeader; - jsonConfig[config_key::responsePacketMagicHeader] = responsePacketMagicHeader; - jsonConfig[config_key::underloadPacketMagicHeader] = underloadPacketMagicHeader; - jsonConfig[config_key::transportPacketMagicHeader] = transportPacketMagicHeader; + config.replace("$JUNK_PACKET_COUNT", serverConfigMap.value(config_key::junkPacketCount)); + config.replace("$JUNK_PACKET_MIN_SIZE", serverConfigMap.value(config_key::junkPacketMinSize)); + config.replace("$JUNK_PACKET_MAX_SIZE", serverConfigMap.value(config_key::junkPacketMaxSize)); + config.replace("$INIT_PACKET_JUNK_SIZE", serverConfigMap.value(config_key::initPacketJunkSize)); + config.replace("$RESPONSE_PACKET_JUNK_SIZE", serverConfigMap.value(config_key::responsePacketJunkSize)); + config.replace("$INIT_PACKET_MAGIC_HEADER", serverConfigMap.value(config_key::initPacketMagicHeader)); + config.replace("$RESPONSE_PACKET_MAGIC_HEADER", serverConfigMap.value(config_key::responsePacketMagicHeader)); + config.replace("$UNDERLOAD_PACKET_MAGIC_HEADER", serverConfigMap.value(config_key::underloadPacketMagicHeader)); + config.replace("$TRANSPORT_PACKET_MAGIC_HEADER", serverConfigMap.value(config_key::transportPacketMagicHeader)); + + jsonConfig[config_key::junkPacketCount] = serverConfigMap.value(config_key::junkPacketCount); + jsonConfig[config_key::junkPacketMinSize] = serverConfigMap.value(config_key::junkPacketMinSize); + jsonConfig[config_key::junkPacketMaxSize] = serverConfigMap.value(config_key::junkPacketMaxSize); + jsonConfig[config_key::initPacketJunkSize] = serverConfigMap.value(config_key::initPacketJunkSize); + jsonConfig[config_key::responsePacketJunkSize] = serverConfigMap.value(config_key::responsePacketJunkSize); + jsonConfig[config_key::initPacketMagicHeader] = serverConfigMap.value(config_key::initPacketMagicHeader); + jsonConfig[config_key::responsePacketMagicHeader] = serverConfigMap.value(config_key::responsePacketMagicHeader); + jsonConfig[config_key::underloadPacketMagicHeader] = serverConfigMap.value(config_key::underloadPacketMagicHeader); + jsonConfig[config_key::transportPacketMagicHeader] = serverConfigMap.value(config_key::transportPacketMagicHeader); return QJsonDocument(jsonConfig).toJson(); } diff --git a/client/configurators/wireguard_configurator.cpp b/client/configurators/wireguard_configurator.cpp index dd836a18..5ea042c1 100644 --- a/client/configurators/wireguard_configurator.cpp +++ b/client/configurators/wireguard_configurator.cpp @@ -30,6 +30,9 @@ WireguardConfigurator::WireguardConfigurator(std::shared_ptr settings, : amnezia::protocols::wireguard::serverPskKeyPath; m_configTemplate = m_isAmneziaWireGuard ? ProtocolScriptType::amnezia_wireguard_template : ProtocolScriptType::wireguard_template; + + m_protocolName = m_isAmneziaWireGuard ? config_key::amneziaWireguard : config_key::wireguard; + m_defaultPort = m_isAmneziaWireGuard ? protocols::wireguard::defaultPort : protocols::amneziawireguard::defaultPort; } WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys() @@ -70,10 +73,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon { WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys(); connData.host = credentials.hostName; - connData.port = containerConfig.value(m_isAmneziaWireGuard ? config_key::amneziaWireguard : config_key::wireguard) - .toObject() - .value(config_key::port) - .toString(protocols::wireguard::defaultPort); + connData.port = containerConfig.value(m_protocolName).toObject().value(config_key::port).toString(m_defaultPort); if (connData.clientPrivKey.isEmpty() || connData.clientPubKey.isEmpty()) { if (errorCode) diff --git a/client/configurators/wireguard_configurator.h b/client/configurators/wireguard_configurator.h index 70ed729b..10eecbb4 100644 --- a/client/configurators/wireguard_configurator.h +++ b/client/configurators/wireguard_configurator.h @@ -42,6 +42,8 @@ private: QString m_serverPublicKeyPath; QString m_serverPskKeyPath; amnezia::ProtocolScriptType m_configTemplate; + QString m_protocolName; + QString m_defaultPort; }; #endif // WIREGUARD_CONFIGURATOR_H diff --git a/client/core/servercontroller.cpp b/client/core/servercontroller.cpp index 3b30451f..b5467dac 100644 --- a/client/core/servercontroller.cpp +++ b/client/core/servercontroller.cpp @@ -338,6 +338,10 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c return true; } + if (container == DockerContainer::AmneziaWireGuard) { + return true; + } + return false; } diff --git a/client/ui/models/protocols/amneziaWireGuardConfigModel.cpp b/client/ui/models/protocols/amneziaWireGuardConfigModel.cpp index 9cf4ed14..a1ce4385 100644 --- a/client/ui/models/protocols/amneziaWireGuardConfigModel.cpp +++ b/client/ui/models/protocols/amneziaWireGuardConfigModel.cpp @@ -1,5 +1,7 @@ #include "amneziaWireGuardConfigModel.h" +#include + #include "protocols/protocols_defs.h" AmneziaWireGuardConfigModel::AmneziaWireGuardConfigModel(QObject *parent) : QAbstractListModel(parent) @@ -20,7 +22,27 @@ bool AmneziaWireGuardConfigModel::setData(const QModelIndex &index, const QVaria switch (role) { case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break; - case Roles::CipherRole: m_protocolConfig.insert(config_key::cipher, value.toString()); break; + case Roles::JunkPacketCountRole: m_protocolConfig.insert(config_key::junkPacketCount, value.toString()); break; + case Roles::JunkPacketMinSizeRole: m_protocolConfig.insert(config_key::junkPacketMinSize, value.toString()); break; + case Roles::JunkPacketMaxSizeRole: m_protocolConfig.insert(config_key::junkPacketMaxSize, value.toString()); break; + case Roles::InitPacketJunkSizeRole: + m_protocolConfig.insert(config_key::initPacketJunkSize, value.toString()); + break; + case Roles::ResponsePacketJunkSizeRole: + m_protocolConfig.insert(config_key::responsePacketJunkSize, value.toString()); + break; + case Roles::InitPacketMagicHeaderRole: + m_protocolConfig.insert(config_key::initPacketMagicHeader, value.toString()); + break; + case Roles::ResponsePacketMagicHeaderRole: + m_protocolConfig.insert(config_key::responsePacketMagicHeader, value.toString()); + break; + case Roles::UnderloadPacketMagicHeaderRole: + m_protocolConfig.insert(config_key::underloadPacketMagicHeader, value.toString()); + break; + case Roles::TransportPacketMagicHeaderRole: + m_protocolConfig.insert(config_key::transportPacketMagicHeader, value.toString()); + break; } emit dataChanged(index, index, QList { role }); @@ -34,9 +56,16 @@ QVariant AmneziaWireGuardConfigModel::data(const QModelIndex &index, int role) c } switch (role) { - case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort); - case Roles::CipherRole: - return m_protocolConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher); + case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(); + case Roles::JunkPacketCountRole: return m_protocolConfig.value(config_key::junkPacketCount); + case Roles::JunkPacketMinSizeRole: return m_protocolConfig.value(config_key::junkPacketMinSize); + case Roles::JunkPacketMaxSizeRole: return m_protocolConfig.value(config_key::junkPacketMaxSize); + case Roles::InitPacketJunkSizeRole: return m_protocolConfig.value(config_key::initPacketJunkSize); + case Roles::ResponsePacketJunkSizeRole: return m_protocolConfig.value(config_key::responsePacketJunkSize); + case Roles::InitPacketMagicHeaderRole: return m_protocolConfig.value(config_key::initPacketMagicHeader); + case Roles::ResponsePacketMagicHeaderRole: return m_protocolConfig.value(config_key::responsePacketMagicHeader); + case Roles::UnderloadPacketMagicHeaderRole: return m_protocolConfig.value(config_key::underloadPacketMagicHeader); + case Roles::TransportPacketMagicHeaderRole: return m_protocolConfig.value(config_key::transportPacketMagicHeader); } return QVariant(); @@ -48,14 +77,44 @@ void AmneziaWireGuardConfigModel::updateModel(const QJsonObject &config) m_container = ContainerProps::containerFromString(config.value(config_key::container).toString()); m_fullConfig = config; - QJsonObject protocolConfig = config.value(config_key::wireguard).toObject(); + + QJsonObject protocolConfig = config.value(config_key::amneziaWireguard).toObject(); + + m_protocolConfig[config_key::port] = + protocolConfig.value(config_key::port).toString(protocols::amneziawireguard::defaultPort); + m_protocolConfig[config_key::junkPacketCount] = + protocolConfig.value(config_key::junkPacketCount).toString(protocols::amneziawireguard::defaultJunkPacketCount); + m_protocolConfig[config_key::junkPacketMinSize] = + protocolConfig.value(config_key::junkPacketMinSize) + .toString(protocols::amneziawireguard::defaultJunkPacketMinSize); + m_protocolConfig[config_key::junkPacketMaxSize] = + protocolConfig.value(config_key::junkPacketMaxSize) + .toString(protocols::amneziawireguard::defaultJunkPacketMaxSize); + m_protocolConfig[config_key::initPacketJunkSize] = + protocolConfig.value(config_key::initPacketJunkSize) + .toString(protocols::amneziawireguard::defaultInitPacketJunkSize); + m_protocolConfig[config_key::responsePacketJunkSize] = + protocolConfig.value(config_key::responsePacketJunkSize) + .toString(protocols::amneziawireguard::defaultResponsePacketJunkSize); + m_protocolConfig[config_key::initPacketMagicHeader] = + protocolConfig.value(config_key::initPacketMagicHeader) + .toString(protocols::amneziawireguard::defaultInitPacketMagicHeader); + m_protocolConfig[config_key::responsePacketMagicHeader] = + protocolConfig.value(config_key::responsePacketMagicHeader) + .toString(protocols::amneziawireguard::defaultResponsePacketMagicHeader); + m_protocolConfig[config_key::underloadPacketMagicHeader] = + protocolConfig.value(config_key::underloadPacketMagicHeader) + .toString(protocols::amneziawireguard::defaultUnderloadPacketMagicHeader); + m_protocolConfig[config_key::transportPacketMagicHeader] = + protocolConfig.value(config_key::transportPacketMagicHeader) + .toString(protocols::amneziawireguard::defaultTransportPacketMagicHeader); endResetModel(); } QJsonObject AmneziaWireGuardConfigModel::getConfig() { - m_fullConfig.insert(config_key::wireguard, m_protocolConfig); + m_fullConfig.insert(config_key::amneziaWireguard, m_protocolConfig); return m_fullConfig; } @@ -64,7 +123,15 @@ QHash AmneziaWireGuardConfigModel::roleNames() const QHash roles; roles[PortRole] = "port"; - roles[CipherRole] = "cipher"; + roles[JunkPacketCountRole] = "junkPacketCount"; + roles[JunkPacketMinSizeRole] = "junkPacketMinSize"; + roles[JunkPacketMaxSizeRole] = "junkPacketMaxSize"; + roles[InitPacketJunkSizeRole] = "initPacketJunkSize"; + roles[ResponsePacketJunkSizeRole] = "responsePacketJunkSize"; + roles[InitPacketMagicHeaderRole] = "initPacketMagicHeader"; + roles[ResponsePacketMagicHeaderRole] = "responsePacketMagicHeader"; + roles[UnderloadPacketMagicHeaderRole] = "underloadPacketMagicHeader"; + roles[TransportPacketMagicHeaderRole] = "transportPacketMagicHeader"; return roles; } diff --git a/client/ui/models/protocols/amneziaWireGuardConfigModel.h b/client/ui/models/protocols/amneziaWireGuardConfigModel.h index b798c289..9419d5c9 100644 --- a/client/ui/models/protocols/amneziaWireGuardConfigModel.h +++ b/client/ui/models/protocols/amneziaWireGuardConfigModel.h @@ -13,7 +13,15 @@ class AmneziaWireGuardConfigModel : public QAbstractListModel public: enum Roles { PortRole = Qt::UserRole + 1, - CipherRole + JunkPacketCountRole, + JunkPacketMinSizeRole, + JunkPacketMaxSizeRole, + InitPacketJunkSizeRole, + ResponsePacketJunkSizeRole, + InitPacketMagicHeaderRole, + ResponsePacketMagicHeaderRole, + UnderloadPacketMagicHeaderRole, + TransportPacketMagicHeaderRole }; explicit AmneziaWireGuardConfigModel(QObject *parent = nullptr); diff --git a/client/ui/qml/Components/SettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml index 250ba1eb..df25b492 100644 --- a/client/ui/qml/Components/SettingsContainersListView.qml +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -65,7 +65,7 @@ ListView { break } case ContainerEnum.AmneziaWireGuard: { - WireGuardConfigModel.updateModel(config) + AmneziaWireGuardConfigModel.updateModel(config) PageController.goToPage(PageEnum.PageProtocolAmneziaWireGuardSettings) break } diff --git a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml index 3f80428e..a23e9354 100644 --- a/client/ui/qml/Controls2/TextFieldWithHeaderType.qml +++ b/client/ui/qml/Controls2/TextFieldWithHeaderType.qml @@ -12,6 +12,7 @@ Item { property string headerTextColor: "#878b91" property alias errorText: errorField.text + property bool checkEmptyText: false property string buttonText property string buttonImageSource @@ -98,6 +99,12 @@ Item { root.errorText = "" } + onActiveFocusChanged: { + if (checkEmptyText && textFieldText === "") { + errorText = qsTr("The field can't be empty") + } + } + MouseArea { anchors.fill: parent acceptedButtons: Qt.RightButton diff --git a/client/ui/qml/Pages2/PageProtocolAmneziaWireGuardSettings.qml b/client/ui/qml/Pages2/PageProtocolAmneziaWireGuardSettings.qml index a905f47a..35edb15c 100644 --- a/client/ui/qml/Pages2/PageProtocolAmneziaWireGuardSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolAmneziaWireGuardSettings.qml @@ -4,6 +4,8 @@ import QtQuick.Layouts import SortFilterProxyModel 0.2 +import PageEnum 1.0 + import "./" import "../Controls2" import "../Controls2/TextTypes" @@ -75,6 +77,7 @@ PageType { } TextFieldWithHeaderType { + id: portTextField Layout.fillWidth: true Layout.topMargin: 40 @@ -88,132 +91,175 @@ PageType { port = textFieldText } } + + checkEmptyText: true } TextFieldWithHeaderType { + id: junkPacketCountTextField Layout.fillWidth: true Layout.topMargin: 16 headerText: qsTr("Junk packet count") - textFieldText: port + textFieldText: junkPacketCount + textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { - if (textFieldText !== port) { - port = textFieldText + console.log("1") + if (textFieldText === "") { + textFieldText = "0" + } + + if (textFieldText !== junkPacketCount) { + junkPacketCount = textFieldText } } + + checkEmptyText: true } TextFieldWithHeaderType { + id: junkPacketMinSizeTextField Layout.fillWidth: true Layout.topMargin: 16 headerText: qsTr("Junk packet minimum size") - textFieldText: port + textFieldText: junkPacketMinSize + textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { - if (textFieldText !== port) { - port = textFieldText + if (textFieldText !== junkPacketMinSize) { + junkPacketMinSize = textFieldText } } + + checkEmptyText: true } TextFieldWithHeaderType { + id: junkPacketMaxSizeTextField Layout.fillWidth: true Layout.topMargin: 16 headerText: qsTr("Junk packet maximum size") - textFieldText: port + textFieldText: junkPacketMaxSize + textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { - if (textFieldText !== port) { - port = textFieldText + if (textFieldText !== junkPacketMaxSize) { + junkPacketMaxSize = textFieldText } } + + checkEmptyText: true } TextFieldWithHeaderType { + id: initPacketJunkSizeTextField Layout.fillWidth: true Layout.topMargin: 16 headerText: qsTr("Init packet junk size") - textFieldText: port + textFieldText: initPacketJunkSize + textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { - if (textFieldText !== port) { - port = textFieldText + if (textFieldText !== initPacketJunkSize) { + initPacketJunkSize = textFieldText } } + + checkEmptyText: true } TextFieldWithHeaderType { + id: responsePacketJunkSizeTextField Layout.fillWidth: true Layout.topMargin: 16 headerText: qsTr("Response packet junk size") - textFieldText: port + textFieldText: responsePacketJunkSize + textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { - if (textFieldText !== port) { - port = textFieldText + if (textFieldText !== responsePacketJunkSize) { + responsePacketJunkSize = textFieldText } } + + checkEmptyText: true } TextFieldWithHeaderType { + id: initPacketMagicHeaderTextField Layout.fillWidth: true Layout.topMargin: 16 headerText: qsTr("Init packet magic header") - textFieldText: port + textFieldText: initPacketMagicHeader + textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { - if (textFieldText !== port) { - port = textFieldText + if (textFieldText !== initPacketMagicHeader) { + initPacketMagicHeader = textFieldText } } + + checkEmptyText: true } TextFieldWithHeaderType { + id: responsePacketMagicHeaderTextField Layout.fillWidth: true Layout.topMargin: 16 headerText: qsTr("Response packet magic header") - textFieldText: port + textFieldText: responsePacketMagicHeader + textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { - if (textFieldText !== port) { - port = textFieldText + if (textFieldText !== responsePacketMagicHeader) { + responsePacketMagicHeader = textFieldText } } + + checkEmptyText: true } TextFieldWithHeaderType { + id: transportPacketMagicHeaderTextField Layout.fillWidth: true Layout.topMargin: 16 headerText: qsTr("Transport packet magic header") - textFieldText: port + textFieldText: transportPacketMagicHeader + textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { - if (textFieldText !== port) { - port = textFieldText + if (textFieldText !== transportPacketMagicHeader) { + transportPacketMagicHeader = textFieldText } } + + checkEmptyText: true } TextFieldWithHeaderType { + id: underloadPacketMagicHeaderTextField Layout.fillWidth: true Layout.topMargin: 16 headerText: qsTr("Underload packet magic header") - textFieldText: port + textFieldText: underloadPacketMagicHeader + textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { - if (textFieldText !== port) { - port = textFieldText + if (textFieldText !== underloadPacketMagicHeader) { + underloadPacketMagicHeader = textFieldText } } + + checkEmptyText: true } BasicButtonType { @@ -251,13 +297,24 @@ PageType { Layout.topMargin: 24 Layout.bottomMargin: 24 + enabled: underloadPacketMagicHeaderTextField.errorText === "" && + transportPacketMagicHeaderTextField.errorText === "" && + responsePacketMagicHeaderTextField.errorText === "" && + initPacketMagicHeaderTextField.errorText === "" && + responsePacketJunkSizeTextField.errorText === "" && + initPacketJunkSizeTextField.errorText === "" && + junkPacketMaxSizeTextField.errorText === "" && + junkPacketMinSizeTextField.errorText === "" && + junkPacketCountTextField.errorText === "" && + portTextField.errorText === "" + text: qsTr("Save and Restart Amnezia") onClicked: { forceActiveFocus() -// PageController.showBusyIndicator(true) -// InstallController.updateContainer(ShadowSocksConfigModel.getConfig()) -// PageController.showBusyIndicator(false) + PageController.showBusyIndicator(true) + InstallController.updateContainer(AmneziaWireGuardConfigModel.getConfig()) + PageController.showBusyIndicator(false) } } } From 16fc0617e479a1e4b4b1dd1b320765af4bc117ba Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 6 Oct 2023 17:02:28 +0500 Subject: [PATCH 190/278] renamed amneziawireguard files to awg --- .../{amneziaWireGuardConfigModel.cpp => awgConfigModel.cpp} | 0 .../protocols/{amneziaWireGuardConfigModel.h => awgConfigModel.h} | 0 ...olAmneziaWireGuardSettings.qml => PageProtocolAwgSettings.qml} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename client/ui/models/protocols/{amneziaWireGuardConfigModel.cpp => awgConfigModel.cpp} (100%) rename client/ui/models/protocols/{amneziaWireGuardConfigModel.h => awgConfigModel.h} (100%) rename client/ui/qml/Pages2/{PageProtocolAmneziaWireGuardSettings.qml => PageProtocolAwgSettings.qml} (100%) diff --git a/client/ui/models/protocols/amneziaWireGuardConfigModel.cpp b/client/ui/models/protocols/awgConfigModel.cpp similarity index 100% rename from client/ui/models/protocols/amneziaWireGuardConfigModel.cpp rename to client/ui/models/protocols/awgConfigModel.cpp diff --git a/client/ui/models/protocols/amneziaWireGuardConfigModel.h b/client/ui/models/protocols/awgConfigModel.h similarity index 100% rename from client/ui/models/protocols/amneziaWireGuardConfigModel.h rename to client/ui/models/protocols/awgConfigModel.h diff --git a/client/ui/qml/Pages2/PageProtocolAmneziaWireGuardSettings.qml b/client/ui/qml/Pages2/PageProtocolAwgSettings.qml similarity index 100% rename from client/ui/qml/Pages2/PageProtocolAmneziaWireGuardSettings.qml rename to client/ui/qml/Pages2/PageProtocolAwgSettings.qml From aa4a79934a90e71c9f48a345ddcfcd22224214c8 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 6 Oct 2023 17:19:44 +0500 Subject: [PATCH 191/278] renamed amenziawireguard to awg --- client/amnezia_application.cpp | 4 +-- client/amnezia_application.h | 4 +-- client/configurators/awg_configurator.cpp | 6 ++-- client/configurators/awg_configurator.h | 12 ++++---- client/configurators/vpn_configurator.cpp | 6 ++-- client/configurators/vpn_configurator.h | 4 +-- .../configurators/wireguard_configurator.cpp | 16 +++++------ client/configurators/wireguard_configurator.h | 6 ++-- client/containers/containers_defs.cpp | 10 +++---- client/containers/containers_defs.h | 2 +- client/core/scripts_registry.cpp | 2 +- client/core/servercontroller.cpp | 28 +++++++++---------- client/mozilla/localsocketcontroller.cpp | 4 +-- client/protocols/amneziawireguardprotocol.cpp | 4 +-- client/protocols/amneziawireguardprotocol.h | 12 ++++---- client/protocols/protocols_defs.cpp | 10 +++---- client/protocols/protocols_defs.h | 12 ++++---- client/protocols/vpnprotocol.cpp | 2 +- client/resources.qrc | 2 +- .../amnezia_wireguard/configure_container.sh | 16 +++++------ .../amnezia_wireguard/run_container.sh | 2 +- .../server_scripts/amnezia_wireguard/start.sh | 7 ++--- .../amnezia_wireguard/template.conf | 2 +- client/ui/controllers/importController.cpp | 2 +- client/ui/controllers/pageController.h | 2 +- client/ui/models/protocols/awgConfigModel.cpp | 16 +++++------ client/ui/models/protocols/awgConfigModel.h | 10 +++---- .../Components/SettingsContainersListView.qml | 6 ++-- .../ui/qml/Pages2/PageProtocolAwgSettings.qml | 4 +-- 29 files changed, 105 insertions(+), 108 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index cef722b1..f372a1d8 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -321,8 +321,8 @@ void AmneziaApplication::initModels() m_wireGuardConfigModel.reset(new WireGuardConfigModel(this)); m_engine->rootContext()->setContextProperty("WireGuardConfigModel", m_wireGuardConfigModel.get()); - m_amneziaWireGuardConfigModel.reset(new AmneziaWireGuardConfigModel(this)); - m_engine->rootContext()->setContextProperty("AmneziaWireGuardConfigModel", m_amneziaWireGuardConfigModel.get()); + m_awgConfigModel.reset(new AwgConfigModel(this)); + m_engine->rootContext()->setContextProperty("AwgConfigModel", m_awgConfigModel.get()); #ifdef Q_OS_WINDOWS m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this)); diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 77e50c92..32300421 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -31,7 +31,7 @@ #ifdef Q_OS_WINDOWS #include "ui/models/protocols/ikev2ConfigModel.h" #endif -#include "ui/models/protocols/amneziaWireGuardConfigModel.h" +#include "ui/models/protocols/awgConfigModel.h" #include "ui/models/protocols/openvpnConfigModel.h" #include "ui/models/protocols/shadowsocksConfigModel.h" #include "ui/models/protocols/wireguardConfigModel.h" @@ -99,7 +99,7 @@ private: QScopedPointer m_shadowSocksConfigModel; QScopedPointer m_cloakConfigModel; QScopedPointer m_wireGuardConfigModel; - QScopedPointer m_amneziaWireGuardConfigModel; + QScopedPointer m_awgConfigModel; #ifdef Q_OS_WINDOWS QScopedPointer m_ikev2ConfigModel; #endif diff --git a/client/configurators/awg_configurator.cpp b/client/configurators/awg_configurator.cpp index 6ed1cd1b..8962067a 100644 --- a/client/configurators/awg_configurator.cpp +++ b/client/configurators/awg_configurator.cpp @@ -5,12 +5,12 @@ #include "core/servercontroller.h" -AmneziaWireGuardConfigurator::AmneziaWireGuardConfigurator(std::shared_ptr settings, QObject *parent) +AwgConfigurator::AwgConfigurator(std::shared_ptr settings, QObject *parent) : WireguardConfigurator(settings, true, parent) { } -QString AmneziaWireGuardConfigurator::genAmneziaWireGuardConfig(const ServerCredentials &credentials, +QString AwgConfigurator::genAwgConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode) { @@ -19,7 +19,7 @@ QString AmneziaWireGuardConfigurator::genAmneziaWireGuardConfig(const ServerCred QJsonObject jsonConfig = QJsonDocument::fromJson(config.toUtf8()).object(); ServerController serverController(m_settings); - QString serverConfig = serverController.getTextFileFromContainer(container, credentials, protocols::amneziawireguard::serverConfigPath, errorCode); + QString serverConfig = serverController.getTextFileFromContainer(container, credentials, protocols::awg::serverConfigPath, errorCode); QMap serverConfigMap; auto serverConfigLines = serverConfig.split("\n"); diff --git a/client/configurators/awg_configurator.h b/client/configurators/awg_configurator.h index 02961cf1..cf0f2cae 100644 --- a/client/configurators/awg_configurator.h +++ b/client/configurators/awg_configurator.h @@ -1,18 +1,18 @@ -#ifndef AMNEZIAWIREGUARDCONFIGURATOR_H -#define AMNEZIAWIREGUARDCONFIGURATOR_H +#ifndef AWGCONFIGURATOR_H +#define AWGCONFIGURATOR_H #include #include "wireguard_configurator.h" -class AmneziaWireGuardConfigurator : public WireguardConfigurator +class AwgConfigurator : public WireguardConfigurator { Q_OBJECT public: - AmneziaWireGuardConfigurator(std::shared_ptr settings, QObject *parent = nullptr); + AwgConfigurator(std::shared_ptr settings, QObject *parent = nullptr); - QString genAmneziaWireGuardConfig(const ServerCredentials &credentials, DockerContainer container, + QString genAwgConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr); }; -#endif // AMNEZIAWIREGUARDCONFIGURATOR_H +#endif // AWGCONFIGURATOR_H diff --git a/client/configurators/vpn_configurator.cpp b/client/configurators/vpn_configurator.cpp index 8ab43499..6c5286c2 100644 --- a/client/configurators/vpn_configurator.cpp +++ b/client/configurators/vpn_configurator.cpp @@ -24,7 +24,7 @@ VpnConfigurator::VpnConfigurator(std::shared_ptr settings, QObject *pa wireguardConfigurator = std::shared_ptr(new WireguardConfigurator(settings, false, this)); ikev2Configurator = std::shared_ptr(new Ikev2Configurator(settings, this)); sshConfigurator = std::shared_ptr(new SshConfigurator(settings, this)); - amneziaWireGuardConfigurator = std::shared_ptr(new AmneziaWireGuardConfigurator(settings, this)); + awgConfigurator = std::shared_ptr(new AwgConfigurator(settings, this)); } QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container, @@ -42,8 +42,8 @@ QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentia case Proto::WireGuard: return wireguardConfigurator->genWireguardConfig(credentials, container, containerConfig, errorCode); - case Proto::AmneziaWireGuard: - return amneziaWireGuardConfigurator->genAmneziaWireGuardConfig(credentials, container, containerConfig, errorCode); + case Proto::Awg: + return awgConfigurator->genAwgConfig(credentials, container, containerConfig, errorCode); case Proto::Ikev2: return ikev2Configurator->genIkev2Config(credentials, container, containerConfig, errorCode); diff --git a/client/configurators/vpn_configurator.h b/client/configurators/vpn_configurator.h index d304e4c3..ac89b0e4 100644 --- a/client/configurators/vpn_configurator.h +++ b/client/configurators/vpn_configurator.h @@ -13,7 +13,7 @@ class CloakConfigurator; class WireguardConfigurator; class Ikev2Configurator; class SshConfigurator; -class AmneziaWireGuardConfigurator; +class AwgConfigurator; // Retrieve connection settings from server class VpnConfigurator : ConfiguratorBase @@ -41,7 +41,7 @@ public: std::shared_ptr wireguardConfigurator; std::shared_ptr ikev2Configurator; std::shared_ptr sshConfigurator; - std::shared_ptr amneziaWireGuardConfigurator; + std::shared_ptr awgConfigurator; }; #endif // VPN_CONFIGURATOR_H diff --git a/client/configurators/wireguard_configurator.cpp b/client/configurators/wireguard_configurator.cpp index 5ea042c1..a526e109 100644 --- a/client/configurators/wireguard_configurator.cpp +++ b/client/configurators/wireguard_configurator.cpp @@ -19,20 +19,20 @@ #include "settings.h" #include "utilities.h" -WireguardConfigurator::WireguardConfigurator(std::shared_ptr settings, bool isAmneziaWireGuard, QObject *parent) - : ConfiguratorBase(settings, parent), m_isAmneziaWireGuard(isAmneziaWireGuard) +WireguardConfigurator::WireguardConfigurator(std::shared_ptr settings, bool isAwg, QObject *parent) + : ConfiguratorBase(settings, parent), m_isAwg(isAwg) { - m_serverConfigPath = m_isAmneziaWireGuard ? amnezia::protocols::amneziawireguard::serverConfigPath + m_serverConfigPath = m_isAwg ? amnezia::protocols::awg::serverConfigPath : amnezia::protocols::wireguard::serverConfigPath; - m_serverPublicKeyPath = m_isAmneziaWireGuard ? amnezia::protocols::amneziawireguard::serverPublicKeyPath + m_serverPublicKeyPath = m_isAwg ? amnezia::protocols::awg::serverPublicKeyPath : amnezia::protocols::wireguard::serverPublicKeyPath; - m_serverPskKeyPath = m_isAmneziaWireGuard ? amnezia::protocols::amneziawireguard::serverPskKeyPath + m_serverPskKeyPath = m_isAwg ? amnezia::protocols::awg::serverPskKeyPath : amnezia::protocols::wireguard::serverPskKeyPath; - m_configTemplate = m_isAmneziaWireGuard ? ProtocolScriptType::amnezia_wireguard_template + m_configTemplate = m_isAwg ? ProtocolScriptType::amnezia_wireguard_template : ProtocolScriptType::wireguard_template; - m_protocolName = m_isAmneziaWireGuard ? config_key::amneziaWireguard : config_key::wireguard; - m_defaultPort = m_isAmneziaWireGuard ? protocols::wireguard::defaultPort : protocols::amneziawireguard::defaultPort; + m_protocolName = m_isAwg ? config_key::awg : config_key::wireguard; + m_defaultPort = m_isAwg ? protocols::wireguard::defaultPort : protocols::awg::defaultPort; } WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys() diff --git a/client/configurators/wireguard_configurator.h b/client/configurators/wireguard_configurator.h index 10eecbb4..7f8e1587 100644 --- a/client/configurators/wireguard_configurator.h +++ b/client/configurators/wireguard_configurator.h @@ -12,7 +12,7 @@ class WireguardConfigurator : public ConfiguratorBase { Q_OBJECT public: - WireguardConfigurator(std::shared_ptr settings, bool isAmneziaWireGuard, QObject *parent = nullptr); + WireguardConfigurator(std::shared_ptr settings, bool isAwg, QObject *parent = nullptr); struct ConnectionData { @@ -36,8 +36,8 @@ private: const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr); ConnectionData genClientKeys(); - - bool m_isAmneziaWireGuard; + + bool m_isAwg; QString m_serverConfigPath; QString m_serverPublicKeyPath; QString m_serverPskKeyPath; diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index 0b9e44a2..5f8d2e51 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -84,7 +84,7 @@ QMap ContainerProps::containerHumanNames() { DockerContainer::ShadowSocks, "ShadowSocks" }, { DockerContainer::Cloak, "OpenVPN over Cloak" }, { DockerContainer::WireGuard, "WireGuard" }, - { DockerContainer::AmneziaWireGuard, "Amnezia WireGuard" }, + { DockerContainer::Awg, "Amnezia WireGuard" }, { DockerContainer::Ipsec, QObject::tr("IPsec") }, { DockerContainer::TorWebSite, QObject::tr("Website in Tor network") }, @@ -108,7 +108,7 @@ QMap ContainerProps::containerDescriptions() { DockerContainer::WireGuard, QObject::tr("WireGuard - New popular VPN protocol with high performance, high speed and low power " "consumption. Recommended for regions with low levels of censorship.") }, - { DockerContainer::AmneziaWireGuard, + { DockerContainer::Awg, QObject::tr("WireGuard - New popular VPN protocol with high performance, high speed and low power " "consumption. Recommended for regions with low levels of censorship.") }, { DockerContainer::Ipsec, @@ -148,7 +148,7 @@ amnezia::ServiceType ContainerProps::containerService(DockerContainer c) case DockerContainer::Cloak: return ServiceType::Vpn; case DockerContainer::ShadowSocks: return ServiceType::Vpn; case DockerContainer::WireGuard: return ServiceType::Vpn; - case DockerContainer::AmneziaWireGuard: return ServiceType::Vpn; + case DockerContainer::Awg: return ServiceType::Vpn; case DockerContainer::Ipsec: return ServiceType::Vpn; case DockerContainer::TorWebSite: return ServiceType::Other; case DockerContainer::Dns: return ServiceType::Other; @@ -166,7 +166,7 @@ Proto ContainerProps::defaultProtocol(DockerContainer c) case DockerContainer::Cloak: return Proto::Cloak; case DockerContainer::ShadowSocks: return Proto::ShadowSocks; case DockerContainer::WireGuard: return Proto::WireGuard; - case DockerContainer::AmneziaWireGuard: return Proto::AmneziaWireGuard; + case DockerContainer::Awg: return Proto::Awg; case DockerContainer::Ipsec: return Proto::Ikev2; case DockerContainer::TorWebSite: return Proto::TorWebSite; @@ -186,7 +186,7 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c) switch (c) { case DockerContainer::WireGuard: return true; case DockerContainer::OpenVpn: return true; - case DockerContainer::AmneziaWireGuard: return true; + case DockerContainer::Awg: return true; case DockerContainer::Cloak: return true; // case DockerContainer::ShadowSocks: return true; diff --git a/client/containers/containers_defs.h b/client/containers/containers_defs.h index 774611c8..ce8a2683 100644 --- a/client/containers/containers_defs.h +++ b/client/containers/containers_defs.h @@ -20,7 +20,7 @@ namespace amnezia ShadowSocks, Cloak, WireGuard, - AmneziaWireGuard, + Awg, Ipsec, // non-vpn diff --git a/client/core/scripts_registry.cpp b/client/core/scripts_registry.cpp index 24deb41a..82ae1fce 100644 --- a/client/core/scripts_registry.cpp +++ b/client/core/scripts_registry.cpp @@ -11,7 +11,7 @@ QString amnezia::scriptFolder(amnezia::DockerContainer container) case DockerContainer::Cloak: return QLatin1String("openvpn_cloak"); case DockerContainer::ShadowSocks: return QLatin1String("openvpn_shadowsocks"); case DockerContainer::WireGuard: return QLatin1String("wireguard"); - case DockerContainer::AmneziaWireGuard: return QLatin1String("amnezia_wireguard"); + case DockerContainer::Awg: return QLatin1String("amnezia_wireguard"); case DockerContainer::Ipsec: return QLatin1String("ipsec"); case DockerContainer::TorWebSite: return QLatin1String("website_tor"); diff --git a/client/core/servercontroller.cpp b/client/core/servercontroller.cpp index b5467dac..60691759 100644 --- a/client/core/servercontroller.cpp +++ b/client/core/servercontroller.cpp @@ -337,8 +337,8 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c != newProtoConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort)) return true; } - - if (container == DockerContainer::AmneziaWireGuard) { + + if (container == DockerContainer::Awg) { return true; } @@ -491,7 +491,7 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential const QJsonObject &ssConfig = config.value(ProtocolProps::protoToString(Proto::ShadowSocks)).toObject(); const QJsonObject &wireguarConfig = config.value(ProtocolProps::protoToString(Proto::WireGuard)).toObject(); const QJsonObject &amneziaWireguarConfig = - config.value(ProtocolProps::protoToString(Proto::AmneziaWireGuard)).toObject(); + config.value(ProtocolProps::protoToString(Proto::Awg)).toObject(); const QJsonObject &sftpConfig = config.value(ProtocolProps::protoToString(Proto::Sftp)).toObject(); Vars vars; @@ -589,35 +589,35 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential vars.append({ { "$SFTP_PASSWORD", sftpConfig.value(config_key::password).toString() } }); // Amnezia wireguard vars - vars.append({ { "$AMNEZIAWIREGUARD_SERVER_PORT", - amneziaWireguarConfig.value(config_key::port).toString(protocols::amneziawireguard::defaultPort) } }); + vars.append({ { "$AWG_SERVER_PORT", + amneziaWireguarConfig.value(config_key::port).toString(protocols::awg::defaultPort) } }); vars.append({ { "$JUNK_PACKET_COUNT", amneziaWireguarConfig.value(config_key::junkPacketCount) - .toString(protocols::amneziawireguard::defaultJunkPacketCount) } }); + .toString(protocols::awg::defaultJunkPacketCount) } }); vars.append({ { "$JUNK_PACKET_MIN_SIZE", amneziaWireguarConfig.value(config_key::junkPacketMinSize) - .toString(protocols::amneziawireguard::defaultJunkPacketMinSize) } }); + .toString(protocols::awg::defaultJunkPacketMinSize) } }); vars.append({ { "$JUNK_PACKET_MAX_SIZE", amneziaWireguarConfig.value(config_key::junkPacketMaxSize) - .toString(protocols::amneziawireguard::defaultJunkPacketMaxSize) } }); + .toString(protocols::awg::defaultJunkPacketMaxSize) } }); vars.append({ { "$INIT_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::initPacketJunkSize) - .toString(protocols::amneziawireguard::defaultInitPacketJunkSize) } }); + .toString(protocols::awg::defaultInitPacketJunkSize) } }); vars.append({ { "$RESPONSE_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::responsePacketJunkSize) - .toString(protocols::amneziawireguard::defaultResponsePacketJunkSize) } }); + .toString(protocols::awg::defaultResponsePacketJunkSize) } }); vars.append({ { "$INIT_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::initPacketMagicHeader) - .toString(protocols::amneziawireguard::defaultInitPacketMagicHeader) } }); + .toString(protocols::awg::defaultInitPacketMagicHeader) } }); vars.append({ { "$RESPONSE_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::responsePacketMagicHeader) - .toString(protocols::amneziawireguard::defaultResponsePacketMagicHeader) } }); + .toString(protocols::awg::defaultResponsePacketMagicHeader) } }); vars.append({ { "$UNDERLOAD_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::underloadPacketMagicHeader) - .toString(protocols::amneziawireguard::defaultUnderloadPacketMagicHeader) } }); + .toString(protocols::awg::defaultUnderloadPacketMagicHeader) } }); vars.append({ { "$TRANSPORT_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::transportPacketMagicHeader) - .toString(protocols::amneziawireguard::defaultTransportPacketMagicHeader) } }); + .toString(protocols::awg::defaultTransportPacketMagicHeader) } }); QString serverIp = Utils::getIPAddress(credentials.hostName); if (!serverIp.isEmpty()) { diff --git a/client/mozilla/localsocketcontroller.cpp b/client/mozilla/localsocketcontroller.cpp index d454c16e..2f6fe371 100644 --- a/client/mozilla/localsocketcontroller.cpp +++ b/client/mozilla/localsocketcontroller.cpp @@ -162,8 +162,8 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) { // splitTunnelApps.append(QJsonValue(uri)); // } // json.insert("vpnDisabledApps", splitTunnelApps); - - if (protocolName == amnezia::config_key::amneziaWireguard) { + + if (protocolName == amnezia::config_key::awg) { json.insert(amnezia::config_key::junkPacketCount, wgConfig.value(amnezia::config_key::junkPacketCount)); json.insert(amnezia::config_key::junkPacketMinSize, wgConfig.value(amnezia::config_key::junkPacketMinSize)); json.insert(amnezia::config_key::junkPacketMaxSize, wgConfig.value(amnezia::config_key::junkPacketMaxSize)); diff --git a/client/protocols/amneziawireguardprotocol.cpp b/client/protocols/amneziawireguardprotocol.cpp index cab03da9..e0e51296 100644 --- a/client/protocols/amneziawireguardprotocol.cpp +++ b/client/protocols/amneziawireguardprotocol.cpp @@ -1,10 +1,10 @@ #include "amneziawireguardprotocol.h" -AmneziaWireGuardProtocol::AmneziaWireGuardProtocol(const QJsonObject &configuration, QObject *parent) +Awg::Awg(const QJsonObject &configuration, QObject *parent) : WireguardProtocol(configuration, parent) { } -AmneziaWireGuardProtocol::~AmneziaWireGuardProtocol() +Awg::~Awg() { } diff --git a/client/protocols/amneziawireguardprotocol.h b/client/protocols/amneziawireguardprotocol.h index 329a585e..d7fc9c92 100644 --- a/client/protocols/amneziawireguardprotocol.h +++ b/client/protocols/amneziawireguardprotocol.h @@ -1,17 +1,17 @@ -#ifndef AMNEZIAWIREGUARDPROTOCOL_H -#define AMNEZIAWIREGUARDPROTOCOL_H +#ifndef AWGPROTOCOL_H +#define AWGPROTOCOL_H #include #include "wireguardprotocol.h" -class AmneziaWireGuardProtocol : public WireguardProtocol +class Awg : public WireguardProtocol { Q_OBJECT public: - explicit AmneziaWireGuardProtocol(const QJsonObject &configuration, QObject *parent = nullptr); - virtual ~AmneziaWireGuardProtocol() override; + explicit Awg(const QJsonObject &configuration, QObject *parent = nullptr); + virtual ~Awg() override; }; -#endif // AMNEZIAWIREGUARDPROTOCOL_H +#endif // AWGPROTOCOL_H diff --git a/client/protocols/protocols_defs.cpp b/client/protocols/protocols_defs.cpp index 64cdd003..3982ef9c 100644 --- a/client/protocols/protocols_defs.cpp +++ b/client/protocols/protocols_defs.cpp @@ -89,7 +89,7 @@ amnezia::ServiceType ProtocolProps::protocolService(Proto p) case Proto::Cloak: return ServiceType::Vpn; case Proto::ShadowSocks: return ServiceType::Vpn; case Proto::WireGuard: return ServiceType::Vpn; - case Proto::AmneziaWireGuard: return ServiceType::Vpn; + case Proto::Awg: return ServiceType::Vpn; case Proto::TorWebSite: return ServiceType::Other; case Proto::Dns: return ServiceType::Other; case Proto::FileShare: return ServiceType::Other; @@ -105,7 +105,7 @@ int ProtocolProps::defaultPort(Proto p) case Proto::Cloak: return 443; case Proto::ShadowSocks: return 6789; case Proto::WireGuard: return 51820; - case Proto::AmneziaWireGuard: return 55424; + case Proto::Awg: return 55424; case Proto::Ikev2: return -1; case Proto::L2tp: return -1; @@ -125,7 +125,7 @@ bool ProtocolProps::defaultPortChangeable(Proto p) case Proto::Cloak: return true; case Proto::ShadowSocks: return true; case Proto::WireGuard: return true; - case Proto::AmneziaWireGuard: return true; + case Proto::Awg: return true; case Proto::Ikev2: return false; case Proto::L2tp: return false; @@ -144,7 +144,7 @@ TransportProto ProtocolProps::defaultTransportProto(Proto p) case Proto::Cloak: return TransportProto::Tcp; case Proto::ShadowSocks: return TransportProto::Tcp; case Proto::WireGuard: return TransportProto::Udp; - case Proto::AmneziaWireGuard: return TransportProto::Udp; + case Proto::Awg: return TransportProto::Udp; case Proto::Ikev2: return TransportProto::Udp; case Proto::L2tp: return TransportProto::Udp; // non-vpn @@ -163,7 +163,7 @@ bool ProtocolProps::defaultTransportProtoChangeable(Proto p) case Proto::Cloak: return false; case Proto::ShadowSocks: return false; case Proto::WireGuard: return false; - case Proto::AmneziaWireGuard: return false; + case Proto::Awg: return false; case Proto::Ikev2: return false; case Proto::L2tp: return false; // non-vpn diff --git a/client/protocols/protocols_defs.h b/client/protocols/protocols_defs.h index e26e60a4..d6af132b 100644 --- a/client/protocols/protocols_defs.h +++ b/client/protocols/protocols_defs.h @@ -76,7 +76,7 @@ namespace amnezia constexpr char shadowsocks[] = "shadowsocks"; constexpr char cloak[] = "cloak"; constexpr char sftp[] = "sftp"; - constexpr char amneziaWireguard[] = "amneziawireguard"; + constexpr char awg[] = "awg"; } @@ -151,13 +151,13 @@ namespace amnezia } // namespace sftp - namespace amneziawireguard + namespace awg { constexpr char defaultPort[] = "55424"; - constexpr char serverConfigPath[] = "/opt/amnezia/amneziawireguard/wg0.conf"; - constexpr char serverPublicKeyPath[] = "/opt/amnezia/amneziawireguard/wireguard_server_public_key.key"; - constexpr char serverPskKeyPath[] = "/opt/amnezia/amneziawireguard/wireguard_psk.key"; + constexpr char serverConfigPath[] = "/opt/amnezia/awg/wg0.conf"; + constexpr char serverPublicKeyPath[] = "/opt/amnezia/awg/wireguard_server_public_key.key"; + constexpr char serverPskKeyPath[] = "/opt/amnezia/awg/wireguard_psk.key"; constexpr char defaultJunkPacketCount[] = "3"; constexpr char defaultJunkPacketMinSize[] = "10"; @@ -188,7 +188,7 @@ namespace amnezia ShadowSocks, Cloak, WireGuard, - AmneziaWireGuard, + Awg, Ikev2, L2tp, diff --git a/client/protocols/vpnprotocol.cpp b/client/protocols/vpnprotocol.cpp index 527ede47..2ddc0684 100644 --- a/client/protocols/vpnprotocol.cpp +++ b/client/protocols/vpnprotocol.cpp @@ -113,7 +113,7 @@ VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject & case DockerContainer::Cloak: return new OpenVpnOverCloakProtocol(configuration); case DockerContainer::ShadowSocks: return new ShadowSocksVpnProtocol(configuration); case DockerContainer::WireGuard: return new WireguardProtocol(configuration); - case DockerContainer::AmneziaWireGuard: return new WireguardProtocol(configuration); + case DockerContainer::Awg: return new WireguardProtocol(configuration); #endif default: return nullptr; } diff --git a/client/resources.qrc b/client/resources.qrc index b79ed3d2..1688d79e 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -215,7 +215,7 @@ ui/qml/Controls2/ListViewWithLabelsType.qml ui/qml/Pages2/PageServiceDnsSettings.qml ui/qml/Controls2/TopCloseButtonType.qml - ui/qml/Pages2/PageProtocolAmneziaWireGuardSettings.qml + ui/qml/Pages2/PageProtocolAwgSettings.qml server_scripts/amnezia_wireguard/template.conf server_scripts/amnezia_wireguard/start.sh server_scripts/amnezia_wireguard/configure_container.sh diff --git a/client/server_scripts/amnezia_wireguard/configure_container.sh b/client/server_scripts/amnezia_wireguard/configure_container.sh index 6ebebc4a..322cc38f 100644 --- a/client/server_scripts/amnezia_wireguard/configure_container.sh +++ b/client/server_scripts/amnezia_wireguard/configure_container.sh @@ -1,19 +1,19 @@ -mkdir -p /opt/amnezia/amneziawireguard -cd /opt/amnezia/amneziawireguard +mkdir -p /opt/amnezia/awg +cd /opt/amnezia/awg WIREGUARD_SERVER_PRIVATE_KEY=$(wg genkey) -echo $WIREGUARD_SERVER_PRIVATE_KEY > /opt/amnezia/amneziawireguard/wireguard_server_private_key.key +echo $WIREGUARD_SERVER_PRIVATE_KEY > /opt/amnezia/awg/wireguard_server_private_key.key WIREGUARD_SERVER_PUBLIC_KEY=$(echo $WIREGUARD_SERVER_PRIVATE_KEY | wg pubkey) -echo $WIREGUARD_SERVER_PUBLIC_KEY > /opt/amnezia/amneziawireguard/wireguard_server_public_key.key +echo $WIREGUARD_SERVER_PUBLIC_KEY > /opt/amnezia/awg/wireguard_server_public_key.key WIREGUARD_PSK=$(wg genpsk) -echo $WIREGUARD_PSK > /opt/amnezia/amneziawireguard/wireguard_psk.key +echo $WIREGUARD_PSK > /opt/amnezia/awg/wireguard_psk.key -cat > /opt/amnezia/amneziawireguard/wg0.conf < /opt/amnezia/awg/wg0.conf < #include "protocols/protocols_defs.h" -AmneziaWireGuardConfigModel::AmneziaWireGuardConfigModel(QObject *parent) : QAbstractListModel(parent) +AwgConfigModel::AwgConfigModel(QObject *parent) : QAbstractListModel(parent) { } -int AmneziaWireGuardConfigModel::rowCount(const QModelIndex &parent) const +int AwgConfigModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return 1; } -bool AmneziaWireGuardConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) +bool AwgConfigModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid() || index.row() < 0 || index.row() >= ContainerProps::allContainers().size()) { return false; @@ -49,7 +49,7 @@ bool AmneziaWireGuardConfigModel::setData(const QModelIndex &index, const QVaria return true; } -QVariant AmneziaWireGuardConfigModel::data(const QModelIndex &index, int role) const +QVariant AwgConfigModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= rowCount()) { return false; @@ -71,7 +71,7 @@ QVariant AmneziaWireGuardConfigModel::data(const QModelIndex &index, int role) c return QVariant(); } -void AmneziaWireGuardConfigModel::updateModel(const QJsonObject &config) +void AwgConfigModel::updateModel(const QJsonObject &config) { beginResetModel(); m_container = ContainerProps::containerFromString(config.value(config_key::container).toString()); @@ -112,13 +112,13 @@ void AmneziaWireGuardConfigModel::updateModel(const QJsonObject &config) endResetModel(); } -QJsonObject AmneziaWireGuardConfigModel::getConfig() +QJsonObject AwgConfigModel::getConfig() { m_fullConfig.insert(config_key::amneziaWireguard, m_protocolConfig); return m_fullConfig; } -QHash AmneziaWireGuardConfigModel::roleNames() const +QHash AwgConfigModel::roleNames() const { QHash roles; diff --git a/client/ui/models/protocols/awgConfigModel.h b/client/ui/models/protocols/awgConfigModel.h index 9419d5c9..e67a3708 100644 --- a/client/ui/models/protocols/awgConfigModel.h +++ b/client/ui/models/protocols/awgConfigModel.h @@ -1,12 +1,12 @@ -#ifndef AMNEZIAWIREGUARDCONFIGMODEL_H -#define AMNEZIAWIREGUARDCONFIGMODEL_H +#ifndef AWGCONFIGMODEL_H +#define AWGCONFIGMODEL_H #include #include #include "containers/containers_defs.h" -class AmneziaWireGuardConfigModel : public QAbstractListModel +class AwgConfigModel : public QAbstractListModel { Q_OBJECT @@ -24,7 +24,7 @@ public: TransportPacketMagicHeaderRole }; - explicit AmneziaWireGuardConfigModel(QObject *parent = nullptr); + explicit AwgConfigModel(QObject *parent = nullptr); int rowCount(const QModelIndex &parent = QModelIndex()) const override; @@ -44,4 +44,4 @@ private: QJsonObject m_fullConfig; }; -#endif // AMNEZIAWIREGUARDCONFIGMODEL_H +#endif // AWGCONFIGMODEL_H diff --git a/client/ui/qml/Components/SettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml index df25b492..89eb727e 100644 --- a/client/ui/qml/Components/SettingsContainersListView.qml +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -64,9 +64,9 @@ ListView { // goToPage(PageEnum.PageProtocolWireGuardSettings) break } - case ContainerEnum.AmneziaWireGuard: { - AmneziaWireGuardConfigModel.updateModel(config) - PageController.goToPage(PageEnum.PageProtocolAmneziaWireGuardSettings) + case ContainerEnum.Awg: { + AwgConfigModel.updateModel(config) + PageController.goToPage(PageEnum.PageProtocolAwgSettings) break } case ContainerEnum.Ipsec: { diff --git a/client/ui/qml/Pages2/PageProtocolAwgSettings.qml b/client/ui/qml/Pages2/PageProtocolAwgSettings.qml index 35edb15c..69d34114 100644 --- a/client/ui/qml/Pages2/PageProtocolAwgSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolAwgSettings.qml @@ -52,7 +52,7 @@ PageType { clip: true interactive: false - model: AmneziaWireGuardConfigModel + model: AwgConfigModel delegate: Item { implicitWidth: listview.width @@ -313,7 +313,7 @@ PageType { onClicked: { forceActiveFocus() PageController.showBusyIndicator(true) - InstallController.updateContainer(AmneziaWireGuardConfigModel.getConfig()) + InstallController.updateContainer(AwgConfigModel.getConfig()) PageController.showBusyIndicator(false) } } From 671ca0a66fa1f7fef101f9cf3a835940847e770b Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 6 Oct 2023 17:26:45 +0500 Subject: [PATCH 192/278] renamed amneziawireguard to awg --- client/CMakeLists.txt | 4 ++-- ...awireguardprotocol.cpp => awgprotocol.cpp} | 2 +- ...neziawireguardprotocol.h => awgprotocol.h} | 0 client/ui/models/protocols/awgConfigModel.cpp | 24 +++++++++---------- client/ui/qml/Pages2/PageShare.qml | 2 +- 5 files changed, 16 insertions(+), 16 deletions(-) rename client/protocols/{amneziawireguardprotocol.cpp => awgprotocol.cpp} (77%) rename client/protocols/{amneziawireguardprotocol.h => awgprotocol.h} (100%) diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index f0f71f52..3988f9b5 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -263,7 +263,7 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) ${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.h ${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.h ${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.h - ${CMAKE_CURRENT_LIST_DIR}/protocols/amneziawireguardprotocol.h + ${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.h ) set(SOURCES ${SOURCES} @@ -274,7 +274,7 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) ${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.cpp ${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.cpp ${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.cpp - ${CMAKE_CURRENT_LIST_DIR}/protocols/amneziawireguardprotocol.cpp + ${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.cpp ) endif() diff --git a/client/protocols/amneziawireguardprotocol.cpp b/client/protocols/awgprotocol.cpp similarity index 77% rename from client/protocols/amneziawireguardprotocol.cpp rename to client/protocols/awgprotocol.cpp index e0e51296..64415dbe 100644 --- a/client/protocols/amneziawireguardprotocol.cpp +++ b/client/protocols/awgprotocol.cpp @@ -1,4 +1,4 @@ -#include "amneziawireguardprotocol.h" +#include "awgprotocol.h" Awg::Awg(const QJsonObject &configuration, QObject *parent) : WireguardProtocol(configuration, parent) diff --git a/client/protocols/amneziawireguardprotocol.h b/client/protocols/awgprotocol.h similarity index 100% rename from client/protocols/amneziawireguardprotocol.h rename to client/protocols/awgprotocol.h diff --git a/client/ui/models/protocols/awgConfigModel.cpp b/client/ui/models/protocols/awgConfigModel.cpp index 7e12be0d..7d0277b9 100644 --- a/client/ui/models/protocols/awgConfigModel.cpp +++ b/client/ui/models/protocols/awgConfigModel.cpp @@ -78,43 +78,43 @@ void AwgConfigModel::updateModel(const QJsonObject &config) m_fullConfig = config; - QJsonObject protocolConfig = config.value(config_key::amneziaWireguard).toObject(); + QJsonObject protocolConfig = config.value(config_key::awg).toObject(); m_protocolConfig[config_key::port] = - protocolConfig.value(config_key::port).toString(protocols::amneziawireguard::defaultPort); + protocolConfig.value(config_key::port).toString(protocols::awg::defaultPort); m_protocolConfig[config_key::junkPacketCount] = - protocolConfig.value(config_key::junkPacketCount).toString(protocols::amneziawireguard::defaultJunkPacketCount); + protocolConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount); m_protocolConfig[config_key::junkPacketMinSize] = protocolConfig.value(config_key::junkPacketMinSize) - .toString(protocols::amneziawireguard::defaultJunkPacketMinSize); + .toString(protocols::awg::defaultJunkPacketMinSize); m_protocolConfig[config_key::junkPacketMaxSize] = protocolConfig.value(config_key::junkPacketMaxSize) - .toString(protocols::amneziawireguard::defaultJunkPacketMaxSize); + .toString(protocols::awg::defaultJunkPacketMaxSize); m_protocolConfig[config_key::initPacketJunkSize] = protocolConfig.value(config_key::initPacketJunkSize) - .toString(protocols::amneziawireguard::defaultInitPacketJunkSize); + .toString(protocols::awg::defaultInitPacketJunkSize); m_protocolConfig[config_key::responsePacketJunkSize] = protocolConfig.value(config_key::responsePacketJunkSize) - .toString(protocols::amneziawireguard::defaultResponsePacketJunkSize); + .toString(protocols::awg::defaultResponsePacketJunkSize); m_protocolConfig[config_key::initPacketMagicHeader] = protocolConfig.value(config_key::initPacketMagicHeader) - .toString(protocols::amneziawireguard::defaultInitPacketMagicHeader); + .toString(protocols::awg::defaultInitPacketMagicHeader); m_protocolConfig[config_key::responsePacketMagicHeader] = protocolConfig.value(config_key::responsePacketMagicHeader) - .toString(protocols::amneziawireguard::defaultResponsePacketMagicHeader); + .toString(protocols::awg::defaultResponsePacketMagicHeader); m_protocolConfig[config_key::underloadPacketMagicHeader] = protocolConfig.value(config_key::underloadPacketMagicHeader) - .toString(protocols::amneziawireguard::defaultUnderloadPacketMagicHeader); + .toString(protocols::awg::defaultUnderloadPacketMagicHeader); m_protocolConfig[config_key::transportPacketMagicHeader] = protocolConfig.value(config_key::transportPacketMagicHeader) - .toString(protocols::amneziawireguard::defaultTransportPacketMagicHeader); + .toString(protocols::awg::defaultTransportPacketMagicHeader); endResetModel(); } QJsonObject AwgConfigModel::getConfig() { - m_fullConfig.insert(config_key::amneziaWireguard, m_protocolConfig); + m_fullConfig.insert(config_key::awg, m_protocolConfig); return m_fullConfig; } diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index a03b3717..16759da1 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -317,7 +317,7 @@ PageType { if (index === ContainerProps.containerFromString("amnezia-openvpn")) { root.connectionTypesModel.push(openVpnConnectionFormat) - } else if (index === ContainerProps.containerFromString("amnezia-wireguard")) { + } else if (index === ContainerProps.containerFromString("amnezia-awg")) { root.connectionTypesModel.push(wireGuardConnectionFormat) } } From 445fc6efb1ccd1717530a2bb80b1c41d55839159 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 6 Oct 2023 22:05:48 +0500 Subject: [PATCH 193/278] renamed amneziawireguard to awg in ios controller --- client/platforms/ios/ios_controller.h | 2 +- client/platforms/ios/ios_controller.mm | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/platforms/ios/ios_controller.h b/client/platforms/ios/ios_controller.h index 6d10dc08..68f30ce8 100644 --- a/client/platforms/ios/ios_controller.h +++ b/client/platforms/ios/ios_controller.h @@ -62,7 +62,7 @@ private: bool setupOpenVPN(); bool setupCloak(); bool setupWireGuard(); - bool setupAmneziaWireGuard(); + bool setupAwg(); bool startOpenVPN(const QString &config); bool startWireGuard(const QString &jsonConfig); diff --git a/client/platforms/ios/ios_controller.mm b/client/platforms/ios/ios_controller.mm index 6782c8da..5665ff1d 100644 --- a/client/platforms/ios/ios_controller.mm +++ b/client/platforms/ios/ios_controller.mm @@ -204,8 +204,8 @@ bool IosController::connectVpn(amnezia::Proto proto, const QJsonObject& configur if (proto == amnezia::Proto::WireGuard) { return setupWireGuard(); } - if (proto == amnezia::Proto::AmneziaWireGuard) { - return setupAmneziaWireGuard(); + if (proto == amnezia::Proto::Awg) { + return setupAwg(); } return false; @@ -310,9 +310,9 @@ bool IosController::setupWireGuard() return startWireGuard(wgConfig); } -bool IosController::setupAmneziaWireGuard() +bool IosController::setupAwg() { - QJsonObject config = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::AmneziaWireGuard)].toObject(); + QJsonObject config = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::Awg)].toObject(); QString wgConfig = config[config_key::config].toString(); From 7f2ef65fe6acc1215633d0900cbe2e7a95bf3b92 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Fri, 6 Oct 2023 17:20:41 -0400 Subject: [PATCH 194/278] Update WG to AWG for Android --- client/3rd-prebuilt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/3rd-prebuilt b/client/3rd-prebuilt index 994f4f2b..fbb5f586 160000 --- a/client/3rd-prebuilt +++ b/client/3rd-prebuilt @@ -1 +1 @@ -Subproject commit 994f4f2b030600f2d8dbf9dccf409b1591c9e463 +Subproject commit fbb5f586b94efc3f65edeaf9559c8a5c4e752d66 From bdfa8bfe5b78d4ff55dad27853c2a67cf44350af Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Sat, 7 Oct 2023 09:01:29 -0400 Subject: [PATCH 195/278] AWG Android support --- .../wireguard/config/BadConfigException.java | 9 + .../src/com/wireguard/config/Interface.java | 278 +++++++++++++++++- .../android/src/org/amnezia/vpn/VPNService.kt | 31 +- client/containers/containers_defs.cpp | 1 + 4 files changed, 309 insertions(+), 10 deletions(-) diff --git a/client/android/src/com/wireguard/config/BadConfigException.java b/client/android/src/com/wireguard/config/BadConfigException.java index 33910501..af909b0d 100644 --- a/client/android/src/com/wireguard/config/BadConfigException.java +++ b/client/android/src/com/wireguard/config/BadConfigException.java @@ -70,6 +70,15 @@ public class BadConfigException extends Exception { EXCLUDED_APPLICATIONS("ExcludedApplications"), INCLUDED_APPLICATIONS("IncludedApplications"), LISTEN_PORT("ListenPort"), + JC("Jc"), + JMIN("Jmin"), + JMAX("Jmax"), + S1("S1"), + S2("S2"), + H1("H1"), + H2("H2"), + H3("H3"), + H4("H4"), MTU("MTU"), PERSISTENT_KEEPALIVE("PersistentKeepalive"), PRE_SHARED_KEY("PresharedKey"), diff --git a/client/android/src/com/wireguard/config/Interface.java b/client/android/src/com/wireguard/config/Interface.java index 2594d701..df6b7fb1 100644 --- a/client/android/src/com/wireguard/config/Interface.java +++ b/client/android/src/com/wireguard/config/Interface.java @@ -44,6 +44,15 @@ public final class Interface { private final KeyPair keyPair; private final Optional listenPort; private final Optional mtu; + private final Optional jc; + private final Optional jmin; + private final Optional jmax; + private final Optional s1; + private final Optional s2; + private final Optional h1; + private final Optional h2; + private final Optional h3; + private final Optional h4; private Interface(final Builder builder) { // Defensively copy to ensure immutability even if the Builder is reused. @@ -56,6 +65,15 @@ public final class Interface { keyPair = Objects.requireNonNull(builder.keyPair, "Interfaces must have a private key"); listenPort = builder.listenPort; mtu = builder.mtu; + jc = builder.jc; + jmax = builder.jmax; + jmin = builder.jmin; + s1 = builder.s1; + s2 = builder.s2; + h1 = builder.h1; + h2 = builder.h2; + h3 = builder.h3; + h4 = builder.h4; } /** @@ -95,6 +113,33 @@ public final class Interface { case "privatekey": builder.parsePrivateKey(attribute.getValue()); break; + case "jc": + builder.parseJc(attribute.getValue()); + break; + case "jmin": + builder.parseJmin(attribute.getValue()); + break; + case "jmax": + builder.parseJmax(attribute.getValue()); + break; + case "s1": + builder.parseS1(attribute.getValue()); + break; + case "s2": + builder.parseS2(attribute.getValue()); + break; + case "h1": + builder.parseH1(attribute.getValue()); + break; + case "h2": + builder.parseH2(attribute.getValue()); + break; + case "h3": + builder.parseH3(attribute.getValue()); + break; + case "h4": + builder.parseH4(attribute.getValue()); + break; default: throw new BadConfigException( Section.INTERFACE, Location.TOP_LEVEL, Reason.UNKNOWN_ATTRIBUTE, attribute.getKey()); @@ -111,7 +156,9 @@ public final class Interface { return addresses.equals(other.addresses) && dnsServers.equals(other.dnsServers) && excludedApplications.equals(other.excludedApplications) && includedApplications.equals(other.includedApplications) && keyPair.equals(other.keyPair) - && listenPort.equals(other.listenPort) && mtu.equals(other.mtu); + && listenPort.equals(other.listenPort) && mtu.equals(other.mtu) && jc.equals(other.jc) && jmin.equals(other.jmin) + && jmax.equals(other.jmax) && s1.equals(other.s1) && s2.equals(other.s2) && h1.equals(other.h1) && h2.equals(other.h2) + && h3.equals(other.h3) && h4.equals(other.h4); } /** @@ -180,6 +227,42 @@ public final class Interface { public Optional getMtu() { return mtu; } + + public Optional getJc() { + return jc; + } + + public Optional getJmin() { + return jmin; + } + + public Optional getJmax() { + return jmax; + } + + public Optional getS1() { + return s1; + } + + public Optional getS2() { + return s2; + } + + public Optional getH1() { + return h1; + } + + public Optional getH2() { + return h2; + } + + public Optional getH3() { + return h3; + } + + public Optional getH4() { + return h4; + } @Override public int hashCode() { @@ -191,6 +274,15 @@ public final class Interface { hash = 31 * hash + keyPair.hashCode(); hash = 31 * hash + listenPort.hashCode(); hash = 31 * hash + mtu.hashCode(); + hash = 31 * hash + jc.hashCode(); + hash = 31 * hash + jmin.hashCode(); + hash = 31 * hash + jmax.hashCode(); + hash = 31 * hash + s1.hashCode(); + hash = 31 * hash + s2.hashCode(); + hash = 31 * hash + h1.hashCode(); + hash = 31 * hash + h2.hashCode(); + hash = 31 * hash + h3.hashCode(); + hash = 31 * hash + h4.hashCode(); return hash; } @@ -234,6 +326,19 @@ public final class Interface { .append('\n'); listenPort.ifPresent(lp -> sb.append("ListenPort = ").append(lp).append('\n')); mtu.ifPresent(m -> sb.append("MTU = ").append(m).append('\n')); + + jc.ifPresent(t_jc -> sb.append("Jc = ").append(t_jc).append('\n')); + jmin.ifPresent(t_jmin -> sb.append("Jmin = ").append(t_jmin).append('\n')); + jmax.ifPresent(t_jmax -> sb.append("Jmax = ").append(t_jmax).append('\n')); + + s1.ifPresent(t_s1 -> sb.append("S1 = ").append(t_s1).append('\n')); + s2.ifPresent(t_s2 -> sb.append("S2 = ").append(t_s2).append('\n')); + + h1.ifPresent(t_h1 -> sb.append("H1 = ").append(t_h1).append('\n')); + h2.ifPresent(t_h2 -> sb.append("H2 = ").append(t_h2).append('\n')); + h3.ifPresent(t_h3 -> sb.append("H3 = ").append(t_h3).append('\n')); + h4.ifPresent(t_h4 -> sb.append("H4 = ").append(t_h4).append('\n')); + sb.append("PrivateKey = ").append(keyPair.getPrivateKey().toBase64()).append('\n'); return sb.toString(); } @@ -248,6 +353,18 @@ public final class Interface { final StringBuilder sb = new StringBuilder(); sb.append("private_key=").append(keyPair.getPrivateKey().toHex()).append('\n'); listenPort.ifPresent(lp -> sb.append("listen_port=").append(lp).append('\n')); + + jc.ifPresent(t_jc -> sb.append("jc=").append(t_jc).append('\n')); + jmin.ifPresent(t_jmin -> sb.append("jmin=").append(t_jmin).append('\n')); + jmax.ifPresent(t_jmax -> sb.append("jmax=").append(t_jmax).append('\n')); + + s1.ifPresent(t_s1 -> sb.append("s1=").append(t_s1).append('\n')); + s2.ifPresent(t_s2 -> sb.append("s2=").append(t_s2).append('\n')); + + h1.ifPresent(t_h1 -> sb.append("h1=").append(t_h1).append('\n')); + h2.ifPresent(t_h2 -> sb.append("h2=").append(t_h2).append('\n')); + h3.ifPresent(t_h3 -> sb.append("h3=").append(t_h3).append('\n')); + h4.ifPresent(t_h4 -> sb.append("h4=").append(t_h4).append('\n')); return sb.toString(); } @@ -267,6 +384,17 @@ public final class Interface { private Optional listenPort = Optional.empty(); // Defaults to not present. private Optional mtu = Optional.empty(); + private Optional jc = Optional.empty(); + private Optional jmin = Optional.empty(); + private Optional jmax = Optional.empty(); + + private Optional s1 = Optional.empty(); + private Optional s2 = Optional.empty(); + + private Optional h1 = Optional.empty(); + private Optional h2 = Optional.empty(); + private Optional h3 = Optional.empty(); + private Optional h4 = Optional.empty(); public Builder addAddress(final InetNetwork address) { addresses.add(address); @@ -362,6 +490,78 @@ public final class Interface { } } + public Builder parseJc(final String jc) throws BadConfigException { + try { + return setJc(Integer.parseInt(jc)); + } catch (final NumberFormatException e) { + throw new BadConfigException(Section.INTERFACE, Location.JC, jc, e); + } + } + + public Builder parseJmax(final String jmax) throws BadConfigException { + try { + return setJmax(Integer.parseInt(jmax)); + } catch (final NumberFormatException e) { + throw new BadConfigException(Section.INTERFACE, Location.JMAX, jmax, e); + } + } + + public Builder parseJmin(final String jmin) throws BadConfigException { + try { + return setJmin(Integer.parseInt(jmin)); + } catch (final NumberFormatException e) { + throw new BadConfigException(Section.INTERFACE, Location.JMIN, jmin, e); + } + } + + public Builder parseS1(final String s1) throws BadConfigException { + try { + return setS1(Integer.parseInt(s1)); + } catch (final NumberFormatException e) { + throw new BadConfigException(Section.INTERFACE, Location.S1, s1, e); + } + } + + public Builder parseS2(final String s2) throws BadConfigException { + try { + return setS2(Integer.parseInt(s2)); + } catch (final NumberFormatException e) { + throw new BadConfigException(Section.INTERFACE, Location.S2, s2, e); + } + } + + public Builder parseH1(final String h1) throws BadConfigException { + try { + return setH1(Long.parseLong(h1)); + } catch (final NumberFormatException e) { + throw new BadConfigException(Section.INTERFACE, Location.H1, h1, e); + } + } + + public Builder parseH2(final String h2) throws BadConfigException { + try { + return setH2(Long.parseLong(h2)); + } catch (final NumberFormatException e) { + throw new BadConfigException(Section.INTERFACE, Location.H2, h2, e); + } + } + + public Builder parseH3(final String h3) throws BadConfigException { + try { + return setH3(Long.parseLong(h3)); + } catch (final NumberFormatException e) { + throw new BadConfigException(Section.INTERFACE, Location.H3, h3, e); + } + } + + public Builder parseH4(final String h4) throws BadConfigException { + try { + return setH4(Long.parseLong(h4)); + } catch (final NumberFormatException e) { + throw new BadConfigException(Section.INTERFACE, Location.H4, h4, e); + } + } + public Builder parsePrivateKey(final String privateKey) throws BadConfigException { try { return setKeyPair(new KeyPair(Key.fromBase64(privateKey))); @@ -386,9 +586,81 @@ public final class Interface { public Builder setMtu(final int mtu) throws BadConfigException { if (mtu < 0) throw new BadConfigException( - Section.INTERFACE, Location.LISTEN_PORT, Reason.INVALID_VALUE, String.valueOf(mtu)); + Section.INTERFACE, Location.MTU, Reason.INVALID_VALUE, String.valueOf(mtu)); this.mtu = mtu == 0 ? Optional.empty() : Optional.of(mtu); return this; } + + public Builder setJc(final int jc) throws BadConfigException { + if (jc < 0) + throw new BadConfigException( + Section.INTERFACE, Location.JC, Reason.INVALID_VALUE, String.valueOf(jc)); + this.jc = jc == 0 ? Optional.empty() : Optional.of(jc); + return this; + } + + public Builder setJmin(final int jmin) throws BadConfigException { + if (jmin < 0) + throw new BadConfigException( + Section.INTERFACE, Location.JMIN, Reason.INVALID_VALUE, String.valueOf(jmin)); + this.jmin = jmin == 0 ? Optional.empty() : Optional.of(jmin); + return this; + } + + public Builder setJmax(final int jmax) throws BadConfigException { + if (jmax < 0) + throw new BadConfigException( + Section.INTERFACE, Location.JMAX, Reason.INVALID_VALUE, String.valueOf(jmax)); + this.jmax = jmax == 0 ? Optional.empty() : Optional.of(jmax); + return this; + } + + public Builder setS1(final int s1) throws BadConfigException { + if (s1 < 0) + throw new BadConfigException( + Section.INTERFACE, Location.S1, Reason.INVALID_VALUE, String.valueOf(s1)); + this.s1 = s1 == 0 ? Optional.empty() : Optional.of(s1); + return this; + } + + public Builder setS2(final int s2) throws BadConfigException { + if (s2 < 0) + throw new BadConfigException( + Section.INTERFACE, Location.S2, Reason.INVALID_VALUE, String.valueOf(s2)); + this.s2 = s2 == 0 ? Optional.empty() : Optional.of(s2); + return this; + } + + public Builder setH1(final long h1) throws BadConfigException { + if (h1 < 0) + throw new BadConfigException( + Section.INTERFACE, Location.H1, Reason.INVALID_VALUE, String.valueOf(h1)); + this.h1 = h1 == 0 ? Optional.empty() : Optional.of(h1); + return this; + } + + public Builder setH2(final long h2) throws BadConfigException { + if (h2 < 0) + throw new BadConfigException( + Section.INTERFACE, Location.H2, Reason.INVALID_VALUE, String.valueOf(h2)); + this.h2 = h2 == 0 ? Optional.empty() : Optional.of(h2); + return this; + } + + public Builder setH3(final long h3) throws BadConfigException { + if (h3 < 0) + throw new BadConfigException( + Section.INTERFACE, Location.H3, Reason.INVALID_VALUE, String.valueOf(h3)); + this.h3 = h3 == 0 ? Optional.empty() : Optional.of(h3); + return this; + } + + public Builder setH4(final long h4) throws BadConfigException { + if (h4 < 0) + throw new BadConfigException( + Section.INTERFACE, Location.H4, Reason.INVALID_VALUE, String.valueOf(h4)); + this.h4 = h4 == 0 ? Optional.empty() : Optional.of(h4); + return this; + } } -} +} diff --git a/client/android/src/org/amnezia/vpn/VPNService.kt b/client/android/src/org/amnezia/vpn/VPNService.kt index 082fe412..fe08a3cf 100644 --- a/client/android/src/org/amnezia/vpn/VPNService.kt +++ b/client/android/src/org/amnezia/vpn/VPNService.kt @@ -380,7 +380,10 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { mNetworkState.bindNetworkListener() } "wireguard" -> { - startWireGuard() + startWireGuard("wireguard") + } + "awg" -> { + startWireGuard("awg") } "shadowsocks" -> { startShadowsocks() @@ -457,7 +460,8 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { fun turnOff() { Log.v(tag, "Aman: turnOff....................") when (mProtocol) { - "wireguard" -> { + "wireguard", + "awg" -> { GoBackend.wgTurnOff(currentTunnelHandle) } "cloak", @@ -559,14 +563,14 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { } return parseData } - + /** * Create a Wireguard [Config] from a [json] string - * The [json] will be created in AndroidVpnProtocol.cpp */ - private fun buildWireguardConfig(obj: JSONObject): Config { + private fun buildWireguardConfig(obj: JSONObject, type: String): Config { val confBuilder = Config.Builder() - val wireguardConfigData = obj.getJSONObject("wireguard_config_data") + val wireguardConfigData = obj.getJSONObject(type) val config = parseConfigData(wireguardConfigData.getString("config")) val peerBuilder = Peer.Builder() val peerConfig = config["Peer"]!! @@ -599,6 +603,19 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { ifaceConfig["DNS"]!!.split(",").forEach { ifaceBuilder.addDnsServer(InetNetwork.parse(it.trim()).address) } + + ifaceBuilder.parsePrivateKey(ifaceConfig["PrivateKey"]) + if (type == "awg_config_data") { + ifaceBuilder.parseJc(ifaceConfig["Jc"]) + ifaceBuilder.parseJmin(ifaceConfig["Jmin"]) + ifaceBuilder.parseJmax(ifaceConfig["Jmax"]) + ifaceBuilder.parseS1(ifaceConfig["S1"]) + ifaceBuilder.parseS2(ifaceConfig["S2"]) + ifaceBuilder.parseH1(ifaceConfig["H1"]) + ifaceBuilder.parseH2(ifaceConfig["H2"]) + ifaceBuilder.parseH3(ifaceConfig["H3"]) + ifaceBuilder.parseH4(ifaceConfig["H4"]) + } /*val jExcludedApplication = obj.getJSONArray("excludedApps") (0 until jExcludedApplication.length()).toList().forEach { val appName = jExcludedApplication.get(it).toString() @@ -716,8 +733,8 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { }).start() } - private fun startWireGuard() { - val wireguard_conf = buildWireguardConfig(mConfig!!) + private fun startWireGuard(type: String) { + val wireguard_conf = buildWireguardConfig(mConfig!!, type + "_config_data") Log.i(tag, "startWireGuard: wireguard_conf : $wireguard_conf") if (currentTunnelHandle != -1) { Log.e(tag, "Tunnel already up") diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index 5f8d2e51..0337eb44 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -204,6 +204,7 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c) case DockerContainer::WireGuard: return true; case DockerContainer::OpenVpn: return true; case DockerContainer::ShadowSocks: return true; + case DockerContainer::Awg: return true; case DockerContainer::Cloak: return true; default: return false; } From 357c283437372e827fe3581a7398695f8a86cca8 Mon Sep 17 00:00:00 2001 From: albexk Date: Sun, 8 Oct 2023 20:08:32 +0300 Subject: [PATCH 196/278] Disable android accessibility --- client/main.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/main.cpp b/client/main.cpp index 396b7625..bf861dc2 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -26,6 +26,11 @@ int main(int argc, char *argv[]) AllowSetForegroundWindow(ASFW_ANY); #endif +// QTBUG-95974 QTBUG-95764 QTBUG-102168 +#ifdef Q_OS_ANDROID + qputenv("QT_ANDROID_DISABLE_ACCESSIBILITY", "1"); +#endif + #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) AmneziaApplication app(argc, argv); #else From e01dd2bf57e6edd93bdcd499dd512c5e2c53617b Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 9 Oct 2023 19:16:06 +0500 Subject: [PATCH 197/278] added close application button in settings page --- client/images/controls/x-circle.svg | 5 + client/resources.qrc | 1 + client/translations/amneziavpn_ru.ts | 145 ++++++++++-------- client/translations/amneziavpn_zh_CN.ts | 141 ++++++++++------- client/ui/controllers/pageController.cpp | 5 + client/ui/controllers/pageController.h | 3 +- .../ui/qml/Controls2/LabelWithButtonType.qml | 7 +- client/ui/qml/Pages2/PageSettings.qml | 14 ++ 8 files changed, 205 insertions(+), 116 deletions(-) create mode 100644 client/images/controls/x-circle.svg diff --git a/client/images/controls/x-circle.svg b/client/images/controls/x-circle.svg new file mode 100644 index 00000000..2d3f5b26 --- /dev/null +++ b/client/images/controls/x-circle.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/resources.qrc b/client/resources.qrc index 5b4d6ae7..f0494852 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -215,5 +215,6 @@ ui/qml/Controls2/ListViewWithLabelsType.qml ui/qml/Pages2/PageServiceDnsSettings.qml ui/qml/Controls2/TopCloseButtonType.qml + images/controls/x-circle.svg diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index e0bab018..92061f89 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -26,41 +26,41 @@ ConnectionController - + VPN Protocols is not installed. Please install VPN container at first - + Connection... - + Connected - + Settings updated successfully, Reconnnection... - + Reconnection... - - - + + + Connect - + Disconnection... @@ -130,7 +130,7 @@ ImportController - + Scanned %1 of %2. @@ -139,50 +139,55 @@ InstallController - + %1 installed successfully. - + %1 is already installed on the server. - + +Added containers that were already installed on the server + + + + Already installed containers were found on the server. All installed containers have been added to the application - + Settings updated successfully - + Server '%1' was removed - + All containers from server '%1' have been removed - + %1 has been removed from the server '%2' - + Please login as the user - + Server added successfully @@ -250,12 +255,12 @@ Already installed containers were found on the server. All installed containers PageHome - + VPN protocol - + Servers @@ -771,6 +776,11 @@ Already installed containers were found on the server. All installed containers About AmneziaVPN + + + Close application + + PageSettingsAbout @@ -865,71 +875,76 @@ And if you don't like the app, all the more support it - the donation will + Allow application screenshots + + + + Auto start - + Launch the application every time - + starts - + Start minimized - + Launch application minimized - + Language - + Logging - + Enabled - + Disabled - + Reset settings and remove all data from the application - + Reset settings and remove all data from the application? - + All settings will be reset to default. All installed AmneziaVPN services will still remain on the server. - + Continue Продолжить - + Cancel @@ -1525,17 +1540,17 @@ It's okay as long as it's from someone you trust. PageSetupWizardEasy - + What is the level of internet control in your region? - + Set up a VPN yourself - + I want to choose a VPN protocol @@ -1545,7 +1560,7 @@ It's okay as long as it's from someone you trust. Продолжить - + Set up later @@ -1749,11 +1764,6 @@ It's okay as long as it's from someone you trust. VPN access without the ability to manage the server - - - Full access to server - - Server @@ -1765,13 +1775,17 @@ It's okay as long as it's from someone you trust. - + + File with accessing settings to + + + + Connection to - - + File with connection settings to @@ -1795,29 +1809,30 @@ It's okay as long as it's from someone you trust. Full access + + + Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the servers, as well as change settings. + + Servers - - Protocols - - - - + + Protocol - - + + Connection format - + Share @@ -2433,6 +2448,16 @@ It's okay as long as it's from someone you trust. error 0x%1: %2 + + + WireGuard Configuration Highlighter + + + + + &Randomize colors + + SelectLanguageDrawer @@ -2459,22 +2484,22 @@ It's okay as long as it's from someone you trust. SettingsController - + Software version - + All settings have been reset to default values - + Cached profiles cleared - + Backup file is corrupted @@ -2504,7 +2529,7 @@ It's okay as long as it's from someone you trust. - Show content + Show connection settings diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index 37e27786..6c57e9f1 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -27,40 +27,40 @@ ConnectionController - - - + + + Connect 连接 - + VPN Protocols is not installed. Please install VPN container at first 不存在VPN协议,请先安装 - + Connection... 连接中 - + Connected 已连接 - + Reconnection... 重连中 - + Disconnection... 断开中 - + Settings updated successfully, Reconnnection... 配置已更新,重连中 @@ -130,7 +130,7 @@ ImportController - + Scanned %1 of %2. 扫描 %1 of %2. @@ -147,41 +147,46 @@ - + %1 installed successfully. %1 安装成功。 - + %1 is already installed on the server. 服务器上已经安装 %1。 - + +Added containers that were already installed on the server + + + + Already installed containers were found on the server. All installed containers have been added to the application 在服务上发现已经安装协议并添加到应用程序 - + Settings updated successfully 配置更新成功 - + Server '%1' was removed 已移除服务器 '%1' - + All containers from server '%1' have been removed 服务器 '%1' 的所有容器已移除 - + %1 has been removed from the server '%2' %1 已从服务器 '%2' 上移除 @@ -202,12 +207,12 @@ Already installed containers were found on the server. All installed containers 协议已从 - + Please login as the user 请以用户身份登录 - + Server added successfully 服务器添加成功 @@ -275,12 +280,12 @@ Already installed containers were found on the server. All installed containers PageHome - + VPN protocol VPN协议 - + Servers 服务器 @@ -797,6 +802,11 @@ Already installed containers were found on the server. All installed containers About AmneziaVPN 关于 + + + Close application + + PageSettingsAbout @@ -892,71 +902,76 @@ And if you don't like the app, all the more support it - the donation will + Allow application screenshots + + + + Auto start 自动运行 - + Launch the application every time 总是在系统 - + starts 启动时自动运行运用程序 - + Start minimized 最小化 - + Launch application minimized 开启应用程序时窗口最小化 - + Language 语言 - + Logging 日志 - + Enabled 开启 - + Disabled 禁用 - + Reset settings and remove all data from the application 重置并清理应用的所有数据 - + Reset settings and remove all data from the application? 重置并清理应用的所有数据? - + All settings will be reset to default. All installed AmneziaVPN services will still remain on the server. 所有配置恢复为默认值。在服务器上保留所有已安装的AmneziaVPN服务。 - + Continue 继续 - + Cancel 取消 @@ -1561,17 +1576,17 @@ It's okay as long as it's from someone you trust. PageSetupWizardEasy - + What is the level of internet control in your region? 您所在地区的互联网控制力度如何? - + Set up a VPN yourself 自己架设VPN - + I want to choose a VPN protocol 我想选择VPN协议 @@ -1581,7 +1596,7 @@ It's okay as long as it's from someone you trust. 继续 - + Set up later 稍后设置 @@ -1807,8 +1822,12 @@ It's okay as long as it's from someone you trust. + Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the servers, as well as change settings. + + + Full access to server - 获得服务器完整授权 + 获得服务器完整授权 @@ -1827,33 +1846,37 @@ It's okay as long as it's from someone you trust. - + File with accessing settings to + + + + File with connection settings to 连接配置文件的内容为: - Protocols - 协议 + 协议 - + + Protocol 协议 - + Connection to 连接到 - - + + Connection format 连接方式 - + Share 共享 @@ -2469,6 +2492,16 @@ It's okay as long as it's from someone you trust. error 0x%1: %2 错误 0x%1: %2 + + + WireGuard Configuration Highlighter + + + + + &Randomize colors + + SelectLanguageDrawer @@ -2495,22 +2528,22 @@ It's okay as long as it's from someone you trust. SettingsController - + Software version 软件版本 - + Backup file is corrupted 备份文件已损坏 - + All settings have been reset to default values 所配置恢复为默认值 - + Cached profiles cleared 缓存的配置文件已清除 @@ -2540,8 +2573,12 @@ It's okay as long as it's from someone you trust. + Show connection settings + + + Show content - 展示内容 + 展示内容 diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/pageController.cpp index cb500618..ed60500a 100644 --- a/client/ui/controllers/pageController.cpp +++ b/client/ui/controllers/pageController.cpp @@ -157,3 +157,8 @@ void PageController::setTriggeredBtConnectButton(bool trigger) { m_isTriggeredByConnectButton = trigger; } + +void PageController::closeApplication() +{ + qApp->quit(); +} diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 70732bd9..69c80fe6 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -84,10 +84,11 @@ public slots: void drawerOpen(); void drawerClose(); - bool isTriggeredByConnectButton(); void setTriggeredBtConnectButton(bool trigger); + void closeApplication(); + signals: void goToPage(PageLoader::PageEnum page, bool slide = true); void goToStartPage(); diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml index bb051f76..6bc45a8c 100644 --- a/client/ui/qml/Controls2/LabelWithButtonType.qml +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -17,6 +17,7 @@ Item { property string rightImageSource property string leftImageSource + property bool isLeftImageHoverEnabled: true //todo separete this qml file to 3 property string textColor: "#d7d8db" property string descriptionColor: "#878B91" @@ -42,9 +43,9 @@ Item { visible: leftImageSource ? true : false - Layout.preferredHeight: rightImageSource ? leftImage.implicitHeight : 56 - Layout.preferredWidth: rightImageSource ? leftImage.implicitWidth : 56 - Layout.rightMargin: rightImageSource ? 16 : 0 + Layout.preferredHeight: rightImageSource || !isLeftImageHoverEnabled ? leftImage.implicitHeight : 56 + Layout.preferredWidth: rightImageSource || !isLeftImageHoverEnabled ? leftImage.implicitWidth : 56 + Layout.rightMargin: rightImageSource || !isLeftImageHoverEnabled ? 16 : 0 radius: 12 color: "transparent" diff --git a/client/ui/qml/Pages2/PageSettings.qml b/client/ui/qml/Pages2/PageSettings.qml index e020dc2c..a806d472 100644 --- a/client/ui/qml/Pages2/PageSettings.qml +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -107,6 +107,20 @@ PageType { } DividerType {} + + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Close application") + leftImageSource: "qrc:/images/controls/x-circle.svg" + isLeftImageHoverEnabled: false + + clickedFunction: function() { + PageController.closeApplication() + } + } + + DividerType {} } } } From c08e23085ed9361fd19d75008f99542d59d0d9f4 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Mon, 9 Oct 2023 10:29:42 -0400 Subject: [PATCH 198/278] Fix protocol change from AWG to WG for Android --- client/3rd-prebuilt | 2 +- .../src/com/wireguard/config/Interface.java | 18 ++++++++-------- .../android/src/org/amnezia/vpn/VPNService.kt | 21 +++++++++++++++++-- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/client/3rd-prebuilt b/client/3rd-prebuilt index fbb5f586..c3ca525f 160000 --- a/client/3rd-prebuilt +++ b/client/3rd-prebuilt @@ -1 +1 @@ -Subproject commit fbb5f586b94efc3f65edeaf9559c8a5c4e752d66 +Subproject commit c3ca525f92e57fecdd6047e35b4ccded8b173407 diff --git a/client/android/src/com/wireguard/config/Interface.java b/client/android/src/com/wireguard/config/Interface.java index df6b7fb1..4b561680 100644 --- a/client/android/src/com/wireguard/config/Interface.java +++ b/client/android/src/com/wireguard/config/Interface.java @@ -595,7 +595,7 @@ public final class Interface { if (jc < 0) throw new BadConfigException( Section.INTERFACE, Location.JC, Reason.INVALID_VALUE, String.valueOf(jc)); - this.jc = jc == 0 ? Optional.empty() : Optional.of(jc); + this.jc = Optional.of(jc); return this; } @@ -603,7 +603,7 @@ public final class Interface { if (jmin < 0) throw new BadConfigException( Section.INTERFACE, Location.JMIN, Reason.INVALID_VALUE, String.valueOf(jmin)); - this.jmin = jmin == 0 ? Optional.empty() : Optional.of(jmin); + this.jmin = Optional.of(jmin); return this; } @@ -611,7 +611,7 @@ public final class Interface { if (jmax < 0) throw new BadConfigException( Section.INTERFACE, Location.JMAX, Reason.INVALID_VALUE, String.valueOf(jmax)); - this.jmax = jmax == 0 ? Optional.empty() : Optional.of(jmax); + this.jmax = Optional.of(jmax); return this; } @@ -619,7 +619,7 @@ public final class Interface { if (s1 < 0) throw new BadConfigException( Section.INTERFACE, Location.S1, Reason.INVALID_VALUE, String.valueOf(s1)); - this.s1 = s1 == 0 ? Optional.empty() : Optional.of(s1); + this.s1 = Optional.of(s1); return this; } @@ -627,7 +627,7 @@ public final class Interface { if (s2 < 0) throw new BadConfigException( Section.INTERFACE, Location.S2, Reason.INVALID_VALUE, String.valueOf(s2)); - this.s2 = s2 == 0 ? Optional.empty() : Optional.of(s2); + this.s2 = Optional.of(s2); return this; } @@ -635,7 +635,7 @@ public final class Interface { if (h1 < 0) throw new BadConfigException( Section.INTERFACE, Location.H1, Reason.INVALID_VALUE, String.valueOf(h1)); - this.h1 = h1 == 0 ? Optional.empty() : Optional.of(h1); + this.h1 = Optional.of(h1); return this; } @@ -643,7 +643,7 @@ public final class Interface { if (h2 < 0) throw new BadConfigException( Section.INTERFACE, Location.H2, Reason.INVALID_VALUE, String.valueOf(h2)); - this.h2 = h2 == 0 ? Optional.empty() : Optional.of(h2); + this.h2 = Optional.of(h2); return this; } @@ -651,7 +651,7 @@ public final class Interface { if (h3 < 0) throw new BadConfigException( Section.INTERFACE, Location.H3, Reason.INVALID_VALUE, String.valueOf(h3)); - this.h3 = h3 == 0 ? Optional.empty() : Optional.of(h3); + this.h3 = Optional.of(h3); return this; } @@ -659,7 +659,7 @@ public final class Interface { if (h4 < 0) throw new BadConfigException( Section.INTERFACE, Location.H4, Reason.INVALID_VALUE, String.valueOf(h4)); - this.h4 = h4 == 0 ? Optional.empty() : Optional.of(h4); + this.h4 = Optional.of(h4); return this; } } diff --git a/client/android/src/org/amnezia/vpn/VPNService.kt b/client/android/src/org/amnezia/vpn/VPNService.kt index fe08a3cf..06f58980 100644 --- a/client/android/src/org/amnezia/vpn/VPNService.kt +++ b/client/android/src/org/amnezia/vpn/VPNService.kt @@ -615,6 +615,17 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { ifaceBuilder.parseH2(ifaceConfig["H2"]) ifaceBuilder.parseH3(ifaceConfig["H3"]) ifaceBuilder.parseH4(ifaceConfig["H4"]) + } else { + ifaceBuilder.parseJc("0") + ifaceBuilder.parseJmin("0") + ifaceBuilder.parseJmax("0") + ifaceBuilder.parseS1("0") + ifaceBuilder.parseS2("0") + ifaceBuilder.parseH1("0") + ifaceBuilder.parseH2("0") + ifaceBuilder.parseH3("0") + ifaceBuilder.parseH4("0") + } /*val jExcludedApplication = obj.getJSONArray("excludedApps") (0 until jExcludedApplication.length()).toList().forEach { @@ -745,9 +756,15 @@ class VPNService : BaseVpnService(), LocalDnsService.Interface { val builder = Builder() setupBuilder(wireguard_conf, builder) builder.setSession("Amnezia") + + builder.establish().use { tun -> - if (tun == null) return - currentTunnelHandle = GoBackend.wgTurnOn("Amnezia", tun.detachFd(), wgConfig) + if (tun == null) return + if (type == "awg"){ + currentTunnelHandle = GoBackend.wgTurnOn("awg0", tun.detachFd(), wgConfig) + } else { + currentTunnelHandle = GoBackend.wgTurnOn("amn0", tun.detachFd(), wgConfig) + } } if (currentTunnelHandle < 0) { Log.e(tag, "Activation Error Code -> $currentTunnelHandle") From 042788bec3f8316e9b5ccd73f5b6362ef84cd330 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 9 Oct 2023 20:19:22 +0500 Subject: [PATCH 199/278] moved add new server button to main tabbar --- client/ui/qml/Pages2/PageHome.qml | 13 +------ .../ui/qml/Pages2/PageSettingsServersList.qml | 10 ------ .../qml/Pages2/PageSetupWizardInstalling.qml | 6 +--- client/ui/qml/Pages2/PageStart.qml | 35 ++++++++++++++++--- 4 files changed, 33 insertions(+), 31 deletions(-) diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 3808fb15..519c17a5 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -96,7 +96,7 @@ PageType { id: dragArea anchors.fill: buttonBackground - cursorShape: Qt.PointingHandCursor + cursorShape: buttonContent.state === "collapsed" ? Qt.PointingHandCursor : Qt.ArrowCursor hoverEnabled: true drag.target: buttonContent @@ -385,18 +385,7 @@ PageType { Layout.rightMargin: 16 visible: buttonContent.expandedVisibility - actionButtonImage: "qrc:/images/controls/plus.svg" - headerText: qsTr("Servers") - - actionButtonFunction: function() { - buttonContent.state = "collapsed" - connectionTypeSelection.visible = true - } - } - - ConnectionTypeSelectionDrawer { - id: connectionTypeSelection } } diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index c0807f35..040aafc3 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -34,20 +34,10 @@ PageType { Layout.leftMargin: 16 Layout.rightMargin: 16 - actionButtonImage: "qrc:/images/controls/plus.svg" - headerText: qsTr("Servers") - - actionButtonFunction: function() { - connectionTypeSelection.visible = true - } } } - ConnectionTypeSelectionDrawer { - id: connectionTypeSelection - } - FlickableType { anchors.top: header.bottom anchors.topMargin: 16 diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index f2919398..84f6be89 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -42,11 +42,7 @@ PageType { function onInstallServerFinished(finishedMessage) { PageController.goToStartPage() - if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) { - PageController.restorePageHomeState() - } else if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSettings)) { - PageController.goToPage(PageEnum.PageSettingsServersList, false) - } else { + if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSetupWizardStart)) { PageController.replaceStartPage() } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 43366af7..99132bf1 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -9,6 +9,7 @@ import "./" import "../Controls2" import "../Controls2/TextTypes" import "../Config" +import "../Components" PageType { id: root @@ -17,14 +18,14 @@ PageType { target: PageController function onGoToPageHome() { - tabBar.currentIndex = 0 + tabBar.setCurrentIndex(0) tabBarStackView.goToTabBarPage(PageEnum.PageHome) PageController.updateDrawerRootPage(PageEnum.PageHome) } function onGoToPageSettings() { - tabBar.currentIndex = 2 + tabBar.setCurrentIndex(2) tabBarStackView.goToTabBarPage(PageEnum.PageSettings) PageController.updateDrawerRootPage(PageEnum.PageSettings) @@ -70,6 +71,7 @@ PageType { } function onGoToStartPage() { + connectionTypeSelection.close() while (tabBarStackView.depth > 1) { tabBarStackView.pop() } @@ -120,6 +122,8 @@ PageType { height: root.height - tabBar.implicitHeight function goToTabBarPage(page) { + connectionTypeSelection.close() + var pagePath = PageController.getPagePath(page) tabBarStackView.clear(StackView.Immediate) tabBarStackView.replace(pagePath, { "objectName" : pagePath }, StackView.Immediate) @@ -137,14 +141,16 @@ PageType { TabBar { id: tabBar + property int previousIndex: 0 + anchors.right: parent.right anchors.left: parent.left anchors.bottom: parent.bottom topPadding: 8 bottomPadding: 8 - leftPadding: shareTabButton.visible ? 96 : 128 - rightPadding: shareTabButton.visible ? 96 : 128 + leftPadding: 96 + rightPadding: 96 background: Shape { width: parent.width @@ -171,8 +177,10 @@ PageType { onClicked: { tabBarStackView.goToTabBarPage(PageEnum.PageHome) ServersModel.currentlyProcessedIndex = ServersModel.defaultIndex + tabBar.previousIndex = 0 } } + TabImageButtonType { id: shareTabButton @@ -193,13 +201,24 @@ PageType { image: "qrc:/images/controls/share-2.svg" onClicked: { tabBarStackView.goToTabBarPage(PageEnum.PageShare) + tabBar.previousIndex = 1 } } + TabImageButtonType { isSelected: tabBar.currentIndex === 2 image: "qrc:/images/controls/settings-2.svg" onClicked: { tabBarStackView.goToTabBarPage(PageEnum.PageSettings) + tabBar.previousIndex = 2 + } + } + + TabImageButtonType { + isSelected: tabBar.currentIndex === 3 + image: "qrc:/images/controls/plus.svg" + onClicked: { + connectionTypeSelection.open() } } } @@ -215,4 +234,12 @@ PageType { x: tabBarStackView.width - topCloseButton.width z: 1 } + + ConnectionTypeSelectionDrawer { + id: connectionTypeSelection + + onAboutToHide: { + tabBar.setCurrentIndex(tabBar.previousIndex) + } + } } From d364dbac2ce6c3e1f82bc55950f323915f869b8e Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 9 Oct 2023 22:39:32 +0500 Subject: [PATCH 200/278] now when a new container is installed, it is selected as the default container --- client/translations/amneziavpn_ru.ts | 28 +++++++++---------- client/translations/amneziavpn_zh_CN.ts | 28 +++++++++---------- client/ui/controllers/installController.cpp | 1 + .../qml/Components/HomeContainersListView.qml | 2 -- .../qml/Pages2/PageSetupWizardViewConfig.qml | 6 +--- client/ui/qml/Pages2/PageShare.qml | 2 +- 6 files changed, 31 insertions(+), 36 deletions(-) diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index 92061f89..2f36f748 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -162,32 +162,32 @@ Already installed containers were found on the server. All installed containers - + Settings updated successfully - + Server '%1' was removed - + All containers from server '%1' have been removed - + %1 has been removed from the server '%2' - + Please login as the user - + Server added successfully @@ -260,7 +260,7 @@ Already installed containers were found on the server. All installed containers - + Servers @@ -1343,7 +1343,7 @@ And if you don't like the app, all the more support it - the donation will PageSettingsServersList - + Servers @@ -1568,33 +1568,33 @@ It's okay as long as it's from someone you trust. PageSetupWizardInstalling - + The server has already been added to the application - + Amnesia has detected that your server is currently - + busy installing other software. Amnesia installation - + will pause until the server finishes installing other software - + Installing - + Usually it takes no more than 5 minutes diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index 6c57e9f1..c2376be0 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -171,22 +171,22 @@ Already installed containers were found on the server. All installed containers 在服务上发现已经安装协议并添加到应用程序 - + Settings updated successfully 配置更新成功 - + Server '%1' was removed 已移除服务器 '%1' - + All containers from server '%1' have been removed 服务器 '%1' 的所有容器已移除 - + %1 has been removed from the server '%2' %1 已从服务器 '%2' 上移除 @@ -207,12 +207,12 @@ Already installed containers were found on the server. All installed containers 协议已从 - + Please login as the user 请以用户身份登录 - + Server added successfully 服务器添加成功 @@ -285,7 +285,7 @@ Already installed containers were found on the server. All installed containers VPN协议 - + Servers 服务器 @@ -1378,7 +1378,7 @@ And if you don't like the app, all the more support it - the donation will PageSettingsServersList - + Servers 服务器 @@ -1605,32 +1605,32 @@ It's okay as long as it's from someone you trust. PageSetupWizardInstalling - + Usually it takes no more than 5 minutes 通常不超过5分钟 - + The server has already been added to the application 服务器已添加到应用程序中 - + Amnesia has detected that your server is currently Amnezia 检测到您的服务器当前 - + busy installing other software. Amnesia installation 正安装其他软件。Amnezia安装 - + will pause until the server finishes installing other software 将暂停,直到服务器完成安装其他软件。 - + Installing 安装中 diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 72cc34b6..34917cac 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -183,6 +183,7 @@ void InstallController::installContainer(DockerContainer container, QJsonObject "All installed containers have been added to the application"); } + m_containersModel->setData(m_containersModel->index(0, 0), container, ContainersModel::Roles::IsDefaultRole); emit installContainerFinished(finishMessage, ContainerProps::containerService(container) == ServiceType::Other); return; } diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index 4708128f..86a755c1 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -76,7 +76,6 @@ ListView { (ConnectionController.isConnected || ConnectionController.isConnectionInProgress)) { PageController.showNotificationMessage(qsTr("Reconnect via VPN Procotol: ") + name) PageController.goToPageHome() - menu.visible = false ConnectionController.openConnection() } } else { @@ -84,7 +83,6 @@ ListView { InstallController.setShouldCreateServer(false) PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) containersDropDown.menuVisible = false - menu.visible = false } } diff --git a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml index 2f1fc392..83132bd9 100644 --- a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml +++ b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml @@ -25,11 +25,7 @@ PageType { function onImportFinished() { PageController.goToStartPage() - if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) { - PageController.restorePageHomeState() - } else if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSettings)) { - PageController.goToPage(PageEnum.PageSettingsServersList, false) - } else { + if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSetupWizardStart)) { PageController.replaceStartPage() } } diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 6c284278..2fa5e4a1 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -187,7 +187,7 @@ PageType { drawerHeight: 0.4375 - descriptionText: qsTr("Servers") + descriptionText: qsTr("Server") headerText: qsTr("Server") listView: ListViewWithRadioButtonType { From cb7fe50d46190b59c73793956aed0e00778fa67f Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 9 Oct 2023 22:40:06 +0500 Subject: [PATCH 201/278] added margins for picture with qr code --- client/ui/qml/Components/ShareConnectionDrawer.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 2419b51a..1158dadc 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -213,6 +213,7 @@ DrawerType { Image { anchors.fill: parent + anchors.margins: 2 smooth: false source: ExportController.qrCodesCount ? ExportController.qrCodes[0] : "" From 00be3c3ccc61be2640c39aa69e96d8210ea81c2e Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 9 Oct 2023 22:54:33 +0500 Subject: [PATCH 202/278] corrections to the text --- client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml | 2 +- client/ui/qml/Pages2/PageProtocolRaw.qml | 2 +- client/ui/qml/Pages2/PageSettingsAbout.qml | 4 ++-- client/ui/qml/Pages2/PageSettingsBackup.qml | 2 +- client/ui/qml/Pages2/PageSettingsConnection.qml | 2 +- client/ui/qml/Pages2/PageSettingsServerProtocol.qml | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index 491bdf31..c8469dcb 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -360,7 +360,7 @@ PageType { onClicked: { questionDrawer.headerText = qsTr("Remove OpenVpn from server?") - questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it") + questionDrawer.descriptionText = qsTr("All users who you shared a connection with will no longer be able to connect to it.") questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.noButtonText = qsTr("Cancel") diff --git a/client/ui/qml/Pages2/PageProtocolRaw.qml b/client/ui/qml/Pages2/PageProtocolRaw.qml index 34ca4055..2324c091 100644 --- a/client/ui/qml/Pages2/PageProtocolRaw.qml +++ b/client/ui/qml/Pages2/PageProtocolRaw.qml @@ -174,7 +174,7 @@ PageType { clickedFunction: function() { questionDrawer.headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getCurrentlyProcessedContainerName()) - questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it") + questionDrawer.descriptionText = qsTr("All users who you shared a connection with will no longer be able to connect to it.") questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.noButtonText = qsTr("Cancel") diff --git a/client/ui/qml/Pages2/PageSettingsAbout.qml b/client/ui/qml/Pages2/PageSettingsAbout.qml index e73ef88f..eaa9eb3d 100644 --- a/client/ui/qml/Pages2/PageSettingsAbout.qml +++ b/client/ui/qml/Pages2/PageSettingsAbout.qml @@ -68,8 +68,8 @@ PageType { height: 20 font.pixelSize: 14 - text: qsTr("This is a free and open source application. If you like it, support the developers with a donation. -And if you don't like the app, all the more support it - the donation will be used to improve the app.") + text: qsTr("This is a free and open source application. If you like it, support the developers with a donation. ") + + qsTr("And if you don’t like the application, all the more reason to support it - the donation will be used for the improving the application.") color: "#CCCAC8" } diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml index 7a556dfb..96214893 100644 --- a/client/ui/qml/Pages2/PageSettingsBackup.qml +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -77,7 +77,7 @@ PageType { Layout.fillWidth: true Layout.topMargin: -12 - text: qsTr("It will help you instantly restore connection settings at the next installation") + text: qsTr("You can save your settings to a backup file to restore them the next time you install the application.") color: "#878B91" } diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml index ae5fd7f4..374e1ce4 100644 --- a/client/ui/qml/Pages2/PageSettingsConnection.qml +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -97,7 +97,7 @@ PageType { Layout.fillWidth: true text: qsTr("Split site tunneling") - descriptionText: qsTr("Allows you to connect to some sites through a secure connection, and to others bypassing it") + descriptionText: qsTr("Allows you to choose which sites you want to use the VPN for.") rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml index 998948d1..30758da4 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -114,7 +114,7 @@ PageType { clickedFunction: function() { questionDrawer.headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getCurrentlyProcessedContainerName()) - questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it") + questionDrawer.descriptionText = qsTr("All users who you shared a connection with will no longer be able to connect to it.") questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.noButtonText = qsTr("Cancel") From 4a2706a9d9d9df05767661fbb7ceaeb966ff94b2 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 9 Oct 2023 23:00:53 +0500 Subject: [PATCH 203/278] increased the application version to 4.0.8.1 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 716d6a7f..af4ae898 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.0.7.1 +project(${PROJECT} VERSION 4.0.8.1 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) From bb2d794b6fe596b11321e9fbb0e8cc828ea59c01 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 9 Oct 2023 23:18:24 +0500 Subject: [PATCH 204/278] corrections to the text --- client/containers/containers_defs.cpp | 4 +- client/protocols/protocols_defs.cpp | 2 +- client/translations/amneziavpn_ru.ts | 298 ++++++++++++------ client/translations/amneziavpn_zh_CN.ts | 294 ++++++++++++----- .../ui/qml/Pages2/PageProtocolAwgSettings.qml | 8 +- 5 files changed, 431 insertions(+), 175 deletions(-) diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index 0337eb44..fd13bfe0 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -84,7 +84,7 @@ QMap ContainerProps::containerHumanNames() { DockerContainer::ShadowSocks, "ShadowSocks" }, { DockerContainer::Cloak, "OpenVPN over Cloak" }, { DockerContainer::WireGuard, "WireGuard" }, - { DockerContainer::Awg, "Amnezia WireGuard" }, + { DockerContainer::Awg, "AmneziaWG" }, { DockerContainer::Ipsec, QObject::tr("IPsec") }, { DockerContainer::TorWebSite, QObject::tr("Website in Tor network") }, @@ -131,7 +131,7 @@ QMap ContainerProps::containerDetailedDescriptions() QObject::tr("Container with OpenVpn and ShadowSocks protocols " "configured with traffic masking by Cloak plugin") }, { DockerContainer::WireGuard, QObject::tr("WireGuard container") }, - { DockerContainer::WireGuard, QObject::tr("Amnezia WireGuard container") }, + { DockerContainer::WireGuard, QObject::tr("AmneziaWG container") }, { DockerContainer::Ipsec, QObject::tr("IPsec container") }, { DockerContainer::TorWebSite, QObject::tr("Website in Tor network") }, diff --git a/client/protocols/protocols_defs.cpp b/client/protocols/protocols_defs.cpp index 3982ef9c..b7f6b1d8 100644 --- a/client/protocols/protocols_defs.cpp +++ b/client/protocols/protocols_defs.cpp @@ -66,7 +66,7 @@ QMap ProtocolProps::protocolHumanNames() { Proto::ShadowSocks, "ShadowSocks" }, { Proto::Cloak, "Cloak" }, { Proto::WireGuard, "WireGuard" }, - { Proto::WireGuard, "Amnezia WireGuard" }, + { Proto::WireGuard, "AmneziaWG" }, { Proto::Ikev2, "IKEv2" }, { Proto::L2tp, "L2TP" }, diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index e0bab018..f2e7a811 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -26,41 +26,41 @@ ConnectionController - + VPN Protocols is not installed. Please install VPN container at first - + Connection... - + Connected - + Settings updated successfully, Reconnnection... - + Reconnection... - - - + + + Connect - + Disconnection... @@ -122,7 +122,7 @@ - + Reconnect via VPN Procotol: @@ -130,7 +130,7 @@ ImportController - + Scanned %1 of %2. @@ -139,50 +139,55 @@ InstallController - + %1 installed successfully. - + %1 is already installed on the server. - + +Added containers that were already installed on the server + + + + Already installed containers were found on the server. All installed containers have been added to the application - + Settings updated successfully - + Server '%1' was removed - + All containers from server '%1' have been removed - + %1 has been removed from the server '%2' - + Please login as the user - + Server added successfully @@ -250,16 +255,104 @@ Already installed containers were found on the server. All installed containers PageHome - + VPN protocol - + Servers + + PageProtocolAwgSettings + + + AmneziaWG settings + + + + + Port + + + + + Junk packet count + + + + + Junk packet minimum size + + + + + Junk packet maximum size + + + + + Init packet junk size + + + + + Response packet junk size + + + + + Init packet magic header + + + + + Response packet magic header + + + + + Transport packet magic header + + + + + Underload packet magic header + + + + + Remove AmneziaWG + + + + + Remove AmneziaWG from server? + + + + + All users who you shared a connection with will no longer be able to connect to it. + + + + + Continue + Продолжить + + + + Cancel + + + + + Save and Restart Amnezia + + + PageProtocolCloakSettings @@ -865,71 +958,76 @@ And if you don't like the app, all the more support it - the donation will + Allow application screenshots + + + + Auto start - + Launch the application every time - + starts - + Start minimized - + Launch application minimized - + Language - + Logging - + Enabled - + Disabled - + Reset settings and remove all data from the application - + Reset settings and remove all data from the application? - + All settings will be reset to default. All installed AmneziaVPN services will still remain on the server. - + Continue Продолжить - + Cancel @@ -1525,17 +1623,17 @@ It's okay as long as it's from someone you trust. PageSetupWizardEasy - + What is the level of internet control in your region? - + Set up a VPN yourself - + I want to choose a VPN protocol @@ -1545,7 +1643,7 @@ It's okay as long as it's from someone you trust. Продолжить - + Set up later @@ -1749,11 +1847,6 @@ It's okay as long as it's from someone you trust. VPN access without the ability to manage the server - - - Full access to server - - Server @@ -1765,13 +1858,17 @@ It's okay as long as it's from someone you trust. - + + File with accessing settings to + + + + Connection to - - + File with connection settings to @@ -1795,29 +1892,30 @@ It's okay as long as it's from someone you trust. Full access + + + Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the servers, as well as change settings. + + Servers - - Protocols - - - - + + Protocol - - + + Connection format - + Share @@ -2273,103 +2371,109 @@ It's okay as long as it's from someone you trust. - + IPsec - + DNS Service - + Sftp file sharing service - - + + Website in Tor network - + Amnezia DNS - + OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its own security protocol with SSL/TLS for key exchange. - + ShadowSocks - masks VPN traffic, making it similar to normal web traffic, but is recognised by analysis systems in some highly censored regions. - + OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against active-probbing detection. Ideal for bypassing blocking in regions with the highest levels of censorship. - + + WireGuard - New popular VPN protocol with high performance, high speed and low power consumption. Recommended for regions with low levels of censorship. - + IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS. - + Deploy a WordPress site on the Tor network in two clicks. - + Replace the current DNS server with your own. This will increase your privacy level. - + Creates a file vault on your server to securely store and transfer files. - + OpenVPN container - + Container with OpenVpn and ShadowSocks - + Container with OpenVpn and ShadowSocks protocols configured with traffic masking by Cloak plugin - + WireGuard container - - IPsec container + + AmneziaWG container + IPsec container + + + + Sftp file sharing service - is secure FTP service - + Sftp service @@ -2433,6 +2537,16 @@ It's okay as long as it's from someone you trust. error 0x%1: %2 + + + WireGuard Configuration Highlighter + + + + + &Randomize colors + + SelectLanguageDrawer @@ -2459,22 +2573,22 @@ It's okay as long as it's from someone you trust. SettingsController - + Software version - + All settings have been reset to default values - + Cached profiles cleared - + Backup file is corrupted @@ -2504,7 +2618,7 @@ It's okay as long as it's from someone you trust. - Show content + Show connection settings @@ -2589,6 +2703,14 @@ It's okay as long as it's from someone you trust. + + TextFieldWithHeaderType + + + The field can't be empty + + + VpnConnection @@ -2643,32 +2765,32 @@ It's okay as long as it's from someone you trust. amnezia::ContainerProps - + Low - + High - + Medium - + Many foreign websites and VPN providers are blocked - + Some foreign sites are blocked, but VPN providers are not blocked - + I just want to increase the level of privacy diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index 37e27786..cbf0caa1 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -27,40 +27,40 @@ ConnectionController - - - + + + Connect 连接 - + VPN Protocols is not installed. Please install VPN container at first 不存在VPN协议,请先安装 - + Connection... 连接中 - + Connected 已连接 - + Reconnection... 重连中 - + Disconnection... 断开中 - + Settings updated successfully, Reconnnection... 配置已更新,重连中 @@ -122,7 +122,7 @@ 当前平台不支持所选协议 - + Reconnect via VPN Procotol: 重连基于VPN协议: @@ -130,7 +130,7 @@ ImportController - + Scanned %1 of %2. 扫描 %1 of %2. @@ -147,41 +147,46 @@ - + %1 installed successfully. %1 安装成功。 - + %1 is already installed on the server. 服务器上已经安装 %1。 - + +Added containers that were already installed on the server + + + + Already installed containers were found on the server. All installed containers have been added to the application 在服务上发现已经安装协议并添加到应用程序 - + Settings updated successfully 配置更新成功 - + Server '%1' was removed 已移除服务器 '%1' - + All containers from server '%1' have been removed 服务器 '%1' 的所有容器已移除 - + %1 has been removed from the server '%2' %1 已从服务器 '%2' 上移除 @@ -202,12 +207,12 @@ Already installed containers were found on the server. All installed containers 协议已从 - + Please login as the user 请以用户身份登录 - + Server added successfully 服务器添加成功 @@ -275,16 +280,104 @@ Already installed containers were found on the server. All installed containers PageHome - + VPN protocol VPN协议 - + Servers 服务器 + + PageProtocolAwgSettings + + + AmneziaWG settings + + + + + Port + 端口 + + + + Junk packet count + + + + + Junk packet minimum size + + + + + Junk packet maximum size + + + + + Init packet junk size + + + + + Response packet junk size + + + + + Init packet magic header + + + + + Response packet magic header + + + + + Transport packet magic header + + + + + Underload packet magic header + + + + + Remove AmneziaWG + + + + + Remove AmneziaWG from server? + + + + + All users who you shared a connection with will no longer be able to connect to it. + + + + + Continue + 继续 + + + + Cancel + 取消 + + + + Save and Restart Amnezia + 保存并重启Amnezia + + PageProtocolCloakSettings @@ -892,71 +985,76 @@ And if you don't like the app, all the more support it - the donation will + Allow application screenshots + + + + Auto start 自动运行 - + Launch the application every time 总是在系统 - + starts 启动时自动运行运用程序 - + Start minimized 最小化 - + Launch application minimized 开启应用程序时窗口最小化 - + Language 语言 - + Logging 日志 - + Enabled 开启 - + Disabled 禁用 - + Reset settings and remove all data from the application 重置并清理应用的所有数据 - + Reset settings and remove all data from the application? 重置并清理应用的所有数据? - + All settings will be reset to default. All installed AmneziaVPN services will still remain on the server. 所有配置恢复为默认值。在服务器上保留所有已安装的AmneziaVPN服务。 - + Continue 继续 - + Cancel 取消 @@ -1561,17 +1659,17 @@ It's okay as long as it's from someone you trust. PageSetupWizardEasy - + What is the level of internet control in your region? 您所在地区的互联网控制力度如何? - + Set up a VPN yourself 自己架设VPN - + I want to choose a VPN protocol 我想选择VPN协议 @@ -1581,7 +1679,7 @@ It's okay as long as it's from someone you trust. 继续 - + Set up later 稍后设置 @@ -1807,8 +1905,12 @@ It's okay as long as it's from someone you trust. + Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the servers, as well as change settings. + + + Full access to server - 获得服务器完整授权 + 获得服务器完整授权 @@ -1827,33 +1929,37 @@ It's okay as long as it's from someone you trust. - + File with accessing settings to + + + + File with connection settings to 连接配置文件的内容为: - Protocols - 协议 + 协议 - + + Protocol 协议 - + Connection to 连接到 - - + + Connection format 连接方式 - + Share 共享 @@ -2104,7 +2210,7 @@ It's okay as long as it's from someone you trust. QObject - + Sftp service Sftp 服务 @@ -2314,98 +2420,104 @@ It's okay as long as it's from someone you trust. 内部错误 - + IPsec - - + + Website in Tor network 在 Tor 网络中架设网站 - + Amnezia DNS - + Sftp file sharing service SFTP文件共享服务 - + OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its own security protocol with SSL/TLS for key exchange. OpenVPN 是最流行的 VPN 协议,具有灵活的配置选项。它使用自己的安全协议与 SSL/TLS 进行密钥交换。 - + ShadowSocks - masks VPN traffic, making it similar to normal web traffic, but is recognised by analysis systems in some highly censored regions. ShadowSocks - 混淆 VPN 流量,使其与正常的 Web 流量相似,但在一些审查力度高的地区可以被分析系统识别。 - + OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against active-probbing detection. Ideal for bypassing blocking in regions with the highest levels of censorship. OpenVPN over Cloak - OpenVPN 与 VPN 具有伪装成网络流量和防止主动探测检测的保护。非常适合绕过审查力度特别强的地区的封锁。 - + + WireGuard - New popular VPN protocol with high performance, high speed and low power consumption. Recommended for regions with low levels of censorship. WireGuard - 新型流行的VPN协议,具有高性能、高速度和低功耗。建议用于审查力度较低的地区 - + IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS. IKEv2 - 现代稳定协议,相比其他协议较快一些,在信号丢失后恢复连接。Android 和 iOS最新版原生支持。 - + Deploy a WordPress site on the Tor network in two clicks. 只需点击两次即可架设 WordPress 网站到 Tor 网络 - + Replace the current DNS server with your own. This will increase your privacy level. 将当前的 DNS 服务器替换为您自己的。这将提高您的隐私级别。 - + Creates a file vault on your server to securely store and transfer files. 在您的服务器上创建文件库以安全地存储和传输文件 - + OpenVPN container OpenVPN容器 - + Container with OpenVpn and ShadowSocks 带有 OpenVpn 和 ShadowSocks 的容器 - + Container with OpenVpn and ShadowSocks protocols configured with traffic masking by Cloak plugin 具有 OpenVpn 和 ShadowSocks 协议的容器,通过 Cloak 插件配置混淆流量 - + WireGuard container WireGuard 容器 - + + AmneziaWG container + + + + IPsec container IPsec 容器 - + DNS Service DNS 服务 - + Sftp file sharing service - is secure FTP service Sftp 文件共享服务 - 安全的 FTP 服务 @@ -2469,6 +2581,16 @@ It's okay as long as it's from someone you trust. error 0x%1: %2 错误 0x%1: %2 + + + WireGuard Configuration Highlighter + + + + + &Randomize colors + + SelectLanguageDrawer @@ -2495,22 +2617,22 @@ It's okay as long as it's from someone you trust. SettingsController - + Software version 软件版本 - + Backup file is corrupted 备份文件已损坏 - + All settings have been reset to default values 所配置恢复为默认值 - + Cached profiles cleared 缓存的配置文件已清除 @@ -2540,8 +2662,12 @@ It's okay as long as it's from someone you trust. + Show connection settings + + + Show content - 展示内容 + 展示内容 @@ -2625,6 +2751,14 @@ It's okay as long as it's from someone you trust. 退出 + + TextFieldWithHeaderType + + + The field can't be empty + + + VpnConnection @@ -2679,32 +2813,32 @@ It's okay as long as it's from someone you trust. amnezia::ContainerProps - + Low - + High - + Medium - + I just want to increase the level of privacy 我只是想提高隐私级别 - + Many foreign websites and VPN providers are blocked 大多国外网站和VPN提供商被屏蔽 - + Some foreign sites are blocked, but VPN providers are not blocked 一些国外网站被屏蔽,但VPN提供商未被屏蔽 diff --git a/client/ui/qml/Pages2/PageProtocolAwgSettings.qml b/client/ui/qml/Pages2/PageProtocolAwgSettings.qml index 69d34114..079c85a1 100644 --- a/client/ui/qml/Pages2/PageProtocolAwgSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolAwgSettings.qml @@ -73,7 +73,7 @@ PageType { HeaderType { Layout.fillWidth: true - headerText: qsTr("Amnezia WireGuard settings") + headerText: qsTr("AmneziaWG settings") } TextFieldWithHeaderType { @@ -272,11 +272,11 @@ PageType { pressedColor: Qt.rgba(1, 1, 1, 0.12) textColor: "#EB5757" - text: qsTr("Remove Amnezia WireGuard") + text: qsTr("Remove AmneziaWG") onClicked: { - questionDrawer.headerText = qsTr("Remove Amnezia WireGuard from server?") - questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it") + questionDrawer.headerText = qsTr("Remove AmneziaWG from server?") + questionDrawer.descriptionText = qsTr("All users who you shared a connection with will no longer be able to connect to it.") questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.noButtonText = qsTr("Cancel") From 992961c4888cd848b6ae86fbb3a8def9bb586d1a Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Mon, 9 Oct 2023 16:32:43 -0400 Subject: [PATCH 205/278] Update Windows WG to AWG protocol support --- client/3rd-prebuilt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/3rd-prebuilt b/client/3rd-prebuilt index c3ca525f..15b0ff39 160000 --- a/client/3rd-prebuilt +++ b/client/3rd-prebuilt @@ -1 +1 @@ -Subproject commit c3ca525f92e57fecdd6047e35b4ccded8b173407 +Subproject commit 15b0ff395d9d372339c5ea8ea35cb2715b975ea9 From da02f498509c4f7ba4178ab9fbbdd92caf599647 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Tue, 10 Oct 2023 08:43:56 +0800 Subject: [PATCH 206/278] update x value of topbutton when resize window --- client/ui/qml/Controls2/TopCloseButtonType.qml | 4 ++++ client/ui/qml/Pages2/PageStart.qml | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/client/ui/qml/Controls2/TopCloseButtonType.qml b/client/ui/qml/Controls2/TopCloseButtonType.qml index 4a738214..e29b0be4 100644 --- a/client/ui/qml/Controls2/TopCloseButtonType.qml +++ b/client/ui/qml/Controls2/TopCloseButtonType.qml @@ -5,6 +5,8 @@ import QtQuick.Shapes Popup { id: root + property alias buttonWidth: button.implicitWidth + modal: false closePolicy: Popup.NoAutoClose padding: 4 @@ -20,6 +22,8 @@ Popup { } ImageButtonType { + id: button + image: "qrc:/images/svg/close_black_24dp.svg" imageColor: "#D7D8DB" diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 99132bf1..4af774fa 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -136,6 +136,11 @@ PageType { ServersModel.currentlyProcessedIndex = ServersModel.defaultIndex tabBarStackView.push(pagePath, { "objectName" : pagePath }) } + + onWidthChanged: { + topCloseButton.x = tabBarStackView.x + tabBarStackView.width - + topCloseButton.buttonWidth - topCloseButton.rightPadding + } } TabBar { @@ -231,7 +236,7 @@ PageType { TopCloseButtonType { id: topCloseButton - x: tabBarStackView.width - topCloseButton.width + x: tabBarStackView.width - topCloseButton.buttonWidth - topCloseButton.rightPadding z: 1 } From 9d6559f0d732008387936addf1e9c3061019bf59 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 10 Oct 2023 12:50:41 +0500 Subject: [PATCH 207/278] fixed an error when after the first connection with admin config the container model was not updated --- client/amnezia_application.cpp | 2 + client/translations/amneziavpn_ru.ts | 54 ++++++++-------- client/translations/amneziavpn_zh_CN.ts | 86 ++++++++++++++++--------- client/ui/models/containers_model.cpp | 5 ++ client/ui/models/containers_model.h | 2 + client/vpnconnection.cpp | 1 + client/vpnconnection.h | 2 + 7 files changed, 95 insertions(+), 57 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 5b6d2491..4e6bce2b 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -279,6 +279,8 @@ void AmneziaApplication::initModels() { m_containersModel.reset(new ContainersModel(m_settings, this)); m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get()); + connect(m_vpnConnection.get(), &VpnConnection::newVpnConfigurationCreated, m_containersModel.get(), + &ContainersModel::updateContainersConfig); m_serversModel.reset(new ServersModel(m_settings, this)); m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get()); diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index 47d0510f..27cb25e2 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -4,7 +4,7 @@ AmneziaApplication - + Split tunneling for WireGuard is not implemented, the option was disabled @@ -130,7 +130,7 @@ ImportController - + Scanned %1 of %2. @@ -162,32 +162,32 @@ Already installed containers were found on the server. All installed containers - + Settings updated successfully - + Server '%1' was removed - + All containers from server '%1' have been removed - + %1 has been removed from the server '%2' - + Please login as the user - + Server added successfully @@ -559,7 +559,7 @@ Already installed containers were found on the server. All installed containers - All users with whom you shared a connection will no longer be able to connect to it + All users who you shared a connection with will no longer be able to connect to it. @@ -607,7 +607,7 @@ Already installed containers were found on the server. All installed containers - All users with whom you shared a connection will no longer be able to connect to it + All users who you shared a connection with will no longer be able to connect to it. @@ -879,8 +879,12 @@ Already installed containers were found on the server. All installed containers - This is a free and open source application. If you like it, support the developers with a donation. -And if you don't like the app, all the more support it - the donation will be used to improve the app. + This is a free and open source application. If you like it, support the developers with a donation. + + + + + And if you don’t like the application, all the more reason to support it - the donation will be used for the improving the application. @@ -1056,7 +1060,7 @@ And if you don't like the app, all the more support it - the donation will - It will help you instantly restore connection settings at the next installation + You can save your settings to a backup file to restore them the next time you install the application. @@ -1150,7 +1154,7 @@ And if you don't like the app, all the more support it - the donation will - Allows you to connect to some sites through a secure connection, and to others bypassing it + Allows you to choose which sites you want to use the VPN for. @@ -1414,7 +1418,7 @@ And if you don't like the app, all the more support it - the donation will - All users with whom you shared a connection will no longer be able to connect to it + All users who you shared a connection with will no longer be able to connect to it. @@ -1800,27 +1804,27 @@ It's okay as long as it's from someone you trust. PageSetupWizardViewConfig - + New connection - + Do not use connection code from public sources. It could be created to intercept your data. - + Collapse content - + Show content - + Connect @@ -1853,6 +1857,7 @@ It's okay as long as it's from someone you trust. + Server @@ -1902,11 +1907,6 @@ It's okay as long as it's from someone you trust. Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the servers, as well as change settings. - - - Servers - - @@ -2627,7 +2627,7 @@ It's okay as long as it's from someone you trust. - + To read the QR code in the Amnezia app, select "Add server" → "I have data to connect" → "QR code, key or settings file" @@ -2719,7 +2719,7 @@ It's okay as long as it's from someone you trust. VpnConnection - + Mbps diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index e9d73b46..32d2d742 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -4,7 +4,7 @@ AmneziaApplication - + Split tunneling for WireGuard is not implemented, the option was disabled 未启用选项,还未实现基于WireGuard协议的VPN分流 @@ -30,9 +30,6 @@ - - - Connect 连接 @@ -133,7 +130,7 @@ ImportController - + Scanned %1 of %2. 扫描 %1 of %2. @@ -174,22 +171,22 @@ Already installed containers were found on the server. All installed containers 在服务上发现已经安装协议并添加到应用程序 - + Settings updated successfully 配置更新成功 - + Server '%1' was removed 已移除服务器 '%1' - + All containers from server '%1' have been removed 服务器 '%1' 的所有容器已移除 - + %1 has been removed from the server '%2' %1 已从服务器 '%2' 上移除 @@ -210,12 +207,12 @@ Already installed containers were found on the server. All installed containers 协议已从 - + Please login as the user 请以用户身份登录 - + Server added successfully 服务器添加成功 @@ -587,8 +584,12 @@ Already installed containers were found on the server. All installed containers + All users who you shared a connection with will no longer be able to connect to it. + + + All users with whom you shared a connection will no longer be able to connect to it - 与您共享连接的所有用户将无法再连接到此链接 + 与您共享连接的所有用户将无法再连接到此链接 @@ -633,14 +634,18 @@ Already installed containers were found on the server. All installed containers Remove %1 from server? 从服务器移除 %1 ? + + + All users who you shared a connection with will no longer be able to connect to it. + + from server? 从服务器 - All users with whom you shared a connection will no longer be able to connect to it - 与您共享连接的所有用户将无法再连接到此链接 + 与您共享连接的所有用户将无法再连接到此链接 @@ -907,12 +912,21 @@ Already installed containers were found on the server. All installed containers 捐款 - This is a free and open source application. If you like it, support the developers with a donation. And if you don't like the app, all the more support it - the donation will be used to improve the app. - 这是一个免费且开源的应用软件。如果您喜欢它,请捐助支持我们继续研发。 + 这是一个免费且开源的应用软件。如果您喜欢它,请捐助支持我们继续研发。 如果您不喜欢,请捐助支持我们改进它。 + + + This is a free and open source application. If you like it, support the developers with a donation. + + + + + And if you don’t like the application, all the more reason to support it - the donation will be used for the improving the application. + + Card on Patreon @@ -1085,9 +1099,13 @@ And if you don't like the app, all the more support it - the donation will 配置备份 - It will help you instantly restore connection settings at the next installation - 帮助您在下次安装时立即恢复连接设置 + 帮助您在下次安装时立即恢复连接设置 + + + + You can save your settings to a backup file to restore them the next time you install the application. + @@ -1184,8 +1202,12 @@ And if you don't like the app, all the more support it - the donation will + Allows you to choose which sites you want to use the VPN for. + + + Allows you to connect to some sites through a secure connection, and to others bypassing it - 使用VPN访问指定网站,其他的则绕过 + 使用VPN访问指定网站,其他的则绕过 @@ -1441,6 +1463,11 @@ And if you don't like the app, all the more support it - the donation will Remove 移除 + + + All users who you shared a connection with will no longer be able to connect to it. + + from server? 从服务器 @@ -1451,9 +1478,8 @@ And if you don't like the app, all the more support it - the donation will 从服务器移除 %1 ? - All users with whom you shared a connection will no longer be able to connect to it - 与您共享连接的所有用户将无法再连接到此链接 + 与您共享连接的所有用户将无法再连接到此链接 @@ -1839,27 +1865,27 @@ It's okay as long as it's from someone you trust. PageSetupWizardViewConfig - + New connection 新连接 - + Do not use connection code from public sources. It could be created to intercept your data. 请勿使用公共来源的连接代码。它可以被创建来拦截您的数据。 - + Collapse content - + Show content 展示内容 - + Connect 连接 @@ -1921,11 +1947,11 @@ It's okay as long as it's from someone you trust. 获得服务器完整授权 - Servers - 服务器 + 服务器 + Server 服务器 @@ -2678,7 +2704,7 @@ It's okay as long as it's from someone you trust. 展示内容 - + To read the QR code in the Amnezia app, select "Add server" → "I have data to connect" → "QR code, key or settings file" 要读取 Amnezia 应用程序中的二维码,请选择“添加服务器”→“我有数据要连接”→“二维码、密钥或配置文件” @@ -2770,7 +2796,7 @@ It's okay as long as it's from someone you trust. VpnConnection - + Mbps diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index 6cf855a6..0c99041e 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -228,6 +228,11 @@ bool ContainersModel::isAnyContainerInstalled() return false; } +void ContainersModel::updateContainersConfig() +{ + m_containers = m_settings->containers(m_currentlyProcessedServerIndex); +} + QHash ContainersModel::roleNames() const { QHash roles; diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index 2cc41cbf..cc549bb3 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -65,6 +65,8 @@ public slots: bool isAnyContainerInstalled(); + void updateContainersConfig(); + protected: QHash roleNames() const override; diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index 1cff01e6..46e8be60 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -323,6 +323,7 @@ void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &crede ErrorCode e = ErrorCode::NoError; m_vpnConfiguration = createVpnConfiguration(serverIndex, credentials, container, containerConfig, &e); + emit newVpnConfigurationCreated(); if (e) { emit connectionStateChanged(Vpn::ConnectionState::Error); return; diff --git a/client/vpnconnection.h b/client/vpnconnection.h index 20ee14fa..f6b2343c 100644 --- a/client/vpnconnection.h +++ b/client/vpnconnection.h @@ -79,6 +79,8 @@ signals: void serviceIsNotReady(); + void newVpnConfigurationCreated(); + protected slots: void onBytesChanged(quint64 receivedBytes, quint64 sentBytes); void onConnectionStateChanged(Vpn::ConnectionState state); From 5d59a1a10e19764bff4afc0f2e2e8a48c2972ece Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 10 Oct 2023 12:50:41 +0500 Subject: [PATCH 208/278] fixed an error when after the first connection with admin config the container model was not updated --- client/amnezia_application.cpp | 2 + client/translations/amneziavpn_ru.ts | 42 +++++++-------- client/translations/amneziavpn_zh_CN.ts | 71 +++++++++++++++++-------- client/ui/models/containers_model.cpp | 5 ++ client/ui/models/containers_model.h | 2 + client/vpnconnection.cpp | 1 + client/vpnconnection.h | 2 + 7 files changed, 83 insertions(+), 42 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index d8039d9b..b8af9438 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -279,6 +279,8 @@ void AmneziaApplication::initModels() { m_containersModel.reset(new ContainersModel(m_settings, this)); m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get()); + connect(m_vpnConnection.get(), &VpnConnection::newVpnConfigurationCreated, m_containersModel.get(), + &ContainersModel::updateContainersConfig); m_serversModel.reset(new ServersModel(m_settings, this)); m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get()); diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index 2f36f748..0e1d65df 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -4,7 +4,7 @@ AmneziaApplication - + Split tunneling for WireGuard is not implemented, the option was disabled @@ -130,7 +130,7 @@ ImportController - + Scanned %1 of %2. @@ -471,7 +471,7 @@ Already installed containers were found on the server. All installed containers - All users with whom you shared a connection will no longer be able to connect to it + All users who you shared a connection with will no longer be able to connect to it. @@ -519,7 +519,7 @@ Already installed containers were found on the server. All installed containers - All users with whom you shared a connection will no longer be able to connect to it + All users who you shared a connection with will no longer be able to connect to it. @@ -791,8 +791,12 @@ Already installed containers were found on the server. All installed containers - This is a free and open source application. If you like it, support the developers with a donation. -And if you don't like the app, all the more support it - the donation will be used to improve the app. + This is a free and open source application. If you like it, support the developers with a donation. + + + + + And if you don’t like the application, all the more reason to support it - the donation will be used for the improving the application. @@ -968,7 +972,7 @@ And if you don't like the app, all the more support it - the donation will - It will help you instantly restore connection settings at the next installation + You can save your settings to a backup file to restore them the next time you install the application. @@ -1062,7 +1066,7 @@ And if you don't like the app, all the more support it - the donation will - Allows you to connect to some sites through a secure connection, and to others bypassing it + Allows you to choose which sites you want to use the VPN for. @@ -1326,7 +1330,7 @@ And if you don't like the app, all the more support it - the donation will - All users with whom you shared a connection will no longer be able to connect to it + All users who you shared a connection with will no longer be able to connect to it. @@ -1712,27 +1716,27 @@ It's okay as long as it's from someone you trust. PageSetupWizardViewConfig - + New connection - + Do not use connection code from public sources. It could be created to intercept your data. - + Collapse content - + Show content - + Connect @@ -1765,6 +1769,7 @@ It's okay as long as it's from someone you trust. + Server @@ -1814,11 +1819,6 @@ It's okay as long as it's from someone you trust. Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the servers, as well as change settings. - - - Servers - - @@ -2533,7 +2533,7 @@ It's okay as long as it's from someone you trust. - + To read the QR code in the Amnezia app, select "Add server" → "I have data to connect" → "QR code, key or settings file" @@ -2617,7 +2617,7 @@ It's okay as long as it's from someone you trust. VpnConnection - + Mbps diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index c2376be0..645d281f 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -4,7 +4,7 @@ AmneziaApplication - + Split tunneling for WireGuard is not implemented, the option was disabled 未启用选项,还未实现基于WireGuard协议的VPN分流 @@ -130,7 +130,7 @@ ImportController - + Scanned %1 of %2. 扫描 %1 of %2. @@ -496,8 +496,12 @@ Already installed containers were found on the server. All installed containers + All users who you shared a connection with will no longer be able to connect to it. + + + All users with whom you shared a connection will no longer be able to connect to it - 与您共享连接的所有用户将无法再连接到此链接 + 与您共享连接的所有用户将无法再连接到此链接 @@ -542,14 +546,18 @@ Already installed containers were found on the server. All installed containers Remove %1 from server? 从服务器移除 %1 ? + + + All users who you shared a connection with will no longer be able to connect to it. + + from server? 从服务器 - All users with whom you shared a connection will no longer be able to connect to it - 与您共享连接的所有用户将无法再连接到此链接 + 与您共享连接的所有用户将无法再连接到此链接 @@ -816,12 +824,21 @@ Already installed containers were found on the server. All installed containers 捐款 - This is a free and open source application. If you like it, support the developers with a donation. And if you don't like the app, all the more support it - the donation will be used to improve the app. - 这是一个免费且开源的应用软件。如果您喜欢它,请捐助支持我们继续研发。 + 这是一个免费且开源的应用软件。如果您喜欢它,请捐助支持我们继续研发。 如果您不喜欢,请捐助支持我们改进它。 + + + This is a free and open source application. If you like it, support the developers with a donation. + + + + + And if you don’t like the application, all the more reason to support it - the donation will be used for the improving the application. + + Card on Patreon @@ -994,9 +1011,13 @@ And if you don't like the app, all the more support it - the donation will 配置备份 - It will help you instantly restore connection settings at the next installation - 帮助您在下次安装时立即恢复连接设置 + 帮助您在下次安装时立即恢复连接设置 + + + + You can save your settings to a backup file to restore them the next time you install the application. + @@ -1093,8 +1114,12 @@ And if you don't like the app, all the more support it - the donation will + Allows you to choose which sites you want to use the VPN for. + + + Allows you to connect to some sites through a secure connection, and to others bypassing it - 使用VPN访问指定网站,其他的则绕过 + 使用VPN访问指定网站,其他的则绕过 @@ -1350,6 +1375,11 @@ And if you don't like the app, all the more support it - the donation will Remove 移除 + + + All users who you shared a connection with will no longer be able to connect to it. + + from server? 从服务器 @@ -1360,9 +1390,8 @@ And if you don't like the app, all the more support it - the donation will 从服务器移除 %1 ? - All users with whom you shared a connection will no longer be able to connect to it - 与您共享连接的所有用户将无法再连接到此链接 + 与您共享连接的所有用户将无法再连接到此链接 @@ -1748,27 +1777,27 @@ It's okay as long as it's from someone you trust. PageSetupWizardViewConfig - + New connection 新连接 - + Do not use connection code from public sources. It could be created to intercept your data. 请勿使用公共来源的连接代码。它可以被创建来拦截您的数据。 - + Collapse content - + Show content 展示内容 - + Connect 连接 @@ -1830,11 +1859,11 @@ It's okay as long as it's from someone you trust. 获得服务器完整授权 - Servers - 服务器 + 服务器 + Server 服务器 @@ -2581,7 +2610,7 @@ It's okay as long as it's from someone you trust. 展示内容 - + To read the QR code in the Amnezia app, select "Add server" → "I have data to connect" → "QR code, key or settings file" 要读取 Amnezia 应用程序中的二维码,请选择“添加服务器”→“我有数据要连接”→“二维码、密钥或配置文件” @@ -2665,7 +2694,7 @@ It's okay as long as it's from someone you trust. VpnConnection - + Mbps diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index 6cf855a6..0c99041e 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -228,6 +228,11 @@ bool ContainersModel::isAnyContainerInstalled() return false; } +void ContainersModel::updateContainersConfig() +{ + m_containers = m_settings->containers(m_currentlyProcessedServerIndex); +} + QHash ContainersModel::roleNames() const { QHash roles; diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index 2cc41cbf..cc549bb3 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -65,6 +65,8 @@ public slots: bool isAnyContainerInstalled(); + void updateContainersConfig(); + protected: QHash roleNames() const override; diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index 1cff01e6..46e8be60 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -323,6 +323,7 @@ void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &crede ErrorCode e = ErrorCode::NoError; m_vpnConfiguration = createVpnConfiguration(serverIndex, credentials, container, containerConfig, &e); + emit newVpnConfigurationCreated(); if (e) { emit connectionStateChanged(Vpn::ConnectionState::Error); return; diff --git a/client/vpnconnection.h b/client/vpnconnection.h index 20ee14fa..f6b2343c 100644 --- a/client/vpnconnection.h +++ b/client/vpnconnection.h @@ -79,6 +79,8 @@ signals: void serviceIsNotReady(); + void newVpnConfigurationCreated(); + protected slots: void onBytesChanged(quint64 receivedBytes, quint64 sentBytes); void onConnectionStateChanged(Vpn::ConnectionState state); From fa06dbbd291712c5fa1aba2f32fabf03e45a01e1 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Tue, 10 Oct 2023 08:52:36 -0400 Subject: [PATCH 209/278] Bump Android verion --- client/android/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/android/build.gradle b/client/android/build.gradle index cfc53460..49e378a0 100644 --- a/client/android/build.gradle +++ b/client/android/build.gradle @@ -138,8 +138,8 @@ android { resConfig "en" minSdkVersion = 24 targetSdkVersion = 34 - versionCode 32 // Change to a higher number - versionName "3.0.9" // Change to a higher number + versionCode 36 // Change to a higher number + versionName "4.0.8" // Change to a higher number javaCompileOptions.annotationProcessorOptions.arguments = [ "room.schemaLocation": "${qtAndroidDir}/schemas".toString() From d92729d346e14a7ee8ad3ba646202605d9642789 Mon Sep 17 00:00:00 2001 From: pokamest Date: Wed, 11 Oct 2023 15:30:55 +0100 Subject: [PATCH 210/278] Fix install_docker.sh --- client/server_scripts/install_docker.sh | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/client/server_scripts/install_docker.sh b/client/server_scripts/install_docker.sh index d8284dfd..e780dac5 100644 --- a/client/server_scripts/install_docker.sh +++ b/client/server_scripts/install_docker.sh @@ -5,9 +5,9 @@ else echo "Packet manager not found"; exit 1; fi;\ echo "Dist: $dist, Packet manager: $pm, Docker pkg: $docker_pkg";\ if [ "$dist" = "debian" ]; then export DEBIAN_FRONTEND=noninteractive; fi;\ if ! command -v sudo > /dev/null 2>&1; then $pm update -yq; $pm install -yq sudo; fi;\ -if ! command -v fuser > /dev/null 2>&1; then $pm install -yq psmisc; fi;\ -if ! command -v lsof > /dev/null 2>&1; then $pm install -yq lsof; fi;\ -if ! command -v docker > /dev/null 2>&1; then $pm update -yq; $pm install -yq $docker_pkg;\ +if ! command -v fuser > /dev/null 2>&1; then sudo $pm install -yq psmisc; fi;\ +if ! command -v lsof > /dev/null 2>&1; then sudo $pm install -yq lsof; fi;\ +if ! command -v docker > /dev/null 2>&1; then sudo $pm update -yq; sudo $pm install -yq $docker_pkg;\ if [ "$dist" = "fedora" ] || [ "$dist" = "debian" ]; then sudo systemctl enable docker && sudo systemctl start docker; fi;\ fi;\ if [ "$dist" = "debian" ]; then \ @@ -17,4 +17,3 @@ if [ "$dist" = "debian" ]; then \ fi;\ if ! command -v sudo > /dev/null 2>&1; then echo "Failed to install Docker";exit 1;fi;\ docker --version - From 10435cea69b369fc6c85b633582b6e34ffa6b698 Mon Sep 17 00:00:00 2001 From: pokamest Date: Thu, 12 Oct 2023 01:15:05 +0100 Subject: [PATCH 211/278] Tiny refactoring and text fixes --- .../configurators/wireguard_configurator.cpp | 2 +- client/core/scripts_registry.cpp | 4 +- client/core/scripts_registry.h | 2 +- client/resources.qrc | 10 ++-- .../{amnezia_wireguard => awg}/Dockerfile | 0 .../configure_container.sh | 0 .../run_container.sh | 0 .../{amnezia_wireguard => awg}/start.sh | 0 .../{amnezia_wireguard => awg}/template.conf | 0 client/translations/amneziavpn_ru.ts | 28 ++++------- client/translations/amneziavpn_zh_CN.ts | 48 +++++++++++-------- client/ui/qml/Pages2/PageSettings.qml | 2 + .../ui/qml/Pages2/PageSettingsConnection.qml | 9 ++-- .../qml/Pages2/PageSettingsSplitTunneling.qml | 6 +-- deploy/build_windows.bat | 2 +- 15 files changed, 58 insertions(+), 55 deletions(-) rename client/server_scripts/{amnezia_wireguard => awg}/Dockerfile (100%) rename client/server_scripts/{amnezia_wireguard => awg}/configure_container.sh (100%) rename client/server_scripts/{amnezia_wireguard => awg}/run_container.sh (100%) rename client/server_scripts/{amnezia_wireguard => awg}/start.sh (100%) rename client/server_scripts/{amnezia_wireguard => awg}/template.conf (100%) diff --git a/client/configurators/wireguard_configurator.cpp b/client/configurators/wireguard_configurator.cpp index a526e109..e22c8282 100644 --- a/client/configurators/wireguard_configurator.cpp +++ b/client/configurators/wireguard_configurator.cpp @@ -28,7 +28,7 @@ WireguardConfigurator::WireguardConfigurator(std::shared_ptr settings, : amnezia::protocols::wireguard::serverPublicKeyPath; m_serverPskKeyPath = m_isAwg ? amnezia::protocols::awg::serverPskKeyPath : amnezia::protocols::wireguard::serverPskKeyPath; - m_configTemplate = m_isAwg ? ProtocolScriptType::amnezia_wireguard_template + m_configTemplate = m_isAwg ? ProtocolScriptType::awg_template : ProtocolScriptType::wireguard_template; m_protocolName = m_isAwg ? config_key::awg : config_key::wireguard; diff --git a/client/core/scripts_registry.cpp b/client/core/scripts_registry.cpp index 82ae1fce..f209a2b1 100644 --- a/client/core/scripts_registry.cpp +++ b/client/core/scripts_registry.cpp @@ -11,7 +11,7 @@ QString amnezia::scriptFolder(amnezia::DockerContainer container) case DockerContainer::Cloak: return QLatin1String("openvpn_cloak"); case DockerContainer::ShadowSocks: return QLatin1String("openvpn_shadowsocks"); case DockerContainer::WireGuard: return QLatin1String("wireguard"); - case DockerContainer::Awg: return QLatin1String("amnezia_wireguard"); + case DockerContainer::Awg: return QLatin1String("awg"); case DockerContainer::Ipsec: return QLatin1String("ipsec"); case DockerContainer::TorWebSite: return QLatin1String("website_tor"); @@ -46,7 +46,7 @@ QString amnezia::scriptName(ProtocolScriptType type) case ProtocolScriptType::container_startup: return QLatin1String("start.sh"); case ProtocolScriptType::openvpn_template: return QLatin1String("template.ovpn"); case ProtocolScriptType::wireguard_template: return QLatin1String("template.conf"); - case ProtocolScriptType::amnezia_wireguard_template: return QLatin1String("template.conf"); + case ProtocolScriptType::awg_template: return QLatin1String("template.conf"); } } diff --git a/client/core/scripts_registry.h b/client/core/scripts_registry.h index 5c7a1b6a..02fc94fd 100644 --- a/client/core/scripts_registry.h +++ b/client/core/scripts_registry.h @@ -27,7 +27,7 @@ enum ProtocolScriptType { container_startup, openvpn_template, wireguard_template, - amnezia_wireguard_template + awg_template }; diff --git a/client/resources.qrc b/client/resources.qrc index 1b639266..4c63383c 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -217,10 +217,10 @@ ui/qml/Controls2/TopCloseButtonType.qml images/controls/x-circle.svg ui/qml/Pages2/PageProtocolAwgSettings.qml - server_scripts/amnezia_wireguard/template.conf - server_scripts/amnezia_wireguard/start.sh - server_scripts/amnezia_wireguard/configure_container.sh - server_scripts/amnezia_wireguard/run_container.sh - server_scripts/amnezia_wireguard/Dockerfile + server_scripts/awg/template.conf + server_scripts/awg/start.sh + server_scripts/awg/configure_container.sh + server_scripts/awg/run_container.sh + server_scripts/awg/Dockerfile diff --git a/client/server_scripts/amnezia_wireguard/Dockerfile b/client/server_scripts/awg/Dockerfile similarity index 100% rename from client/server_scripts/amnezia_wireguard/Dockerfile rename to client/server_scripts/awg/Dockerfile diff --git a/client/server_scripts/amnezia_wireguard/configure_container.sh b/client/server_scripts/awg/configure_container.sh similarity index 100% rename from client/server_scripts/amnezia_wireguard/configure_container.sh rename to client/server_scripts/awg/configure_container.sh diff --git a/client/server_scripts/amnezia_wireguard/run_container.sh b/client/server_scripts/awg/run_container.sh similarity index 100% rename from client/server_scripts/amnezia_wireguard/run_container.sh rename to client/server_scripts/awg/run_container.sh diff --git a/client/server_scripts/amnezia_wireguard/start.sh b/client/server_scripts/awg/start.sh similarity index 100% rename from client/server_scripts/amnezia_wireguard/start.sh rename to client/server_scripts/awg/start.sh diff --git a/client/server_scripts/amnezia_wireguard/template.conf b/client/server_scripts/awg/template.conf similarity index 100% rename from client/server_scripts/amnezia_wireguard/template.conf rename to client/server_scripts/awg/template.conf diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index 27cb25e2..c46bebd9 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -1149,7 +1149,12 @@ Already installed containers were found on the server. All installed containers - Split site tunneling + Split tunneling + + + + + App-based split tunneling @@ -1157,11 +1162,6 @@ Already installed containers were found on the server. All installed containers Allows you to choose which sites you want to use the VPN for. - - - Separate application tunneling - - Allows you to use the VPN only for certain applications @@ -1444,17 +1444,17 @@ Already installed containers were found on the server. All installed containers PageSettingsSplitTunneling - Only the addresses in the list must be opened via VPN + Addresses from the list should be accessed via VPN - Addresses from the list should never be opened via VPN + Addresses from the list should not be accessed via VPN - Split site tunneling + Split tunneling @@ -2542,16 +2542,6 @@ It's okay as long as it's from someone you trust. error 0x%1: %2 - - - WireGuard Configuration Highlighter - - - - - &Randomize colors - - SelectLanguageDrawer diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index 32d2d742..b4855a72 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -1197,8 +1197,17 @@ And if you don't like the app, all the more support it - the donation will + Split tunneling + + + + + App-based split tunneling + + + Split site tunneling - 网站级VPN分流 + 网站级VPN分流 @@ -1210,9 +1219,8 @@ And if you don't like the app, all the more support it - the donation will 使用VPN访问指定网站,其他的则绕过 - Separate application tunneling - 应用级VPN分流 + 应用级VPN分流 @@ -1503,19 +1511,31 @@ And if you don't like the app, all the more support it - the donation will PageSettingsSplitTunneling - Only the addresses in the list must be opened via VPN - 仅列表中的地址须通过VPN访问 + 仅列表中的地址须通过VPN访问 + + + Addresses from the list should never be opened via VPN + 勿通过VPN访问列表中的地址 + + + Split site tunneling + 网站级VPN分流 + + + + Addresses from the list should be accessed via VPN + - Addresses from the list should never be opened via VPN - 勿通过VPN访问列表中的地址 + Addresses from the list should not be accessed via VPN + - Split site tunneling - 网站级VPN分流 + Split tunneling + @@ -2615,16 +2635,6 @@ It's okay as long as it's from someone you trust. error 0x%1: %2 错误 0x%1: %2 - - - WireGuard Configuration Highlighter - - - - - &Randomize colors - - SelectLanguageDrawer diff --git a/client/ui/qml/Pages2/PageSettings.qml b/client/ui/qml/Pages2/PageSettings.qml index a806d472..d90f3ec8 100644 --- a/client/ui/qml/Pages2/PageSettings.qml +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -95,6 +95,7 @@ PageType { DividerType {} LabelWithButtonType { + id: about Layout.fillWidth: true text: qsTr("About AmneziaVPN") @@ -110,6 +111,7 @@ PageType { LabelWithButtonType { Layout.fillWidth: true + Layout.preferredHeight: about.height text: qsTr("Close application") leftImageSource: "qrc:/images/controls/x-circle.svg" diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml index 374e1ce4..b5343d24 100644 --- a/client/ui/qml/Pages2/PageSettingsConnection.qml +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -96,8 +96,8 @@ PageType { LabelWithButtonType { Layout.fillWidth: true - text: qsTr("Split site tunneling") - descriptionText: qsTr("Allows you to choose which sites you want to use the VPN for.") + text: qsTr("Site-based split tunneling") + descriptionText: qsTr("Allows you to select which sites you want to access through the VPN") rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { @@ -109,8 +109,9 @@ PageType { LabelWithButtonType { Layout.fillWidth: true + visible: false - text: qsTr("Separate application tunneling") + text: qsTr("App-based split tunneling") descriptionText: qsTr("Allows you to use the VPN only for certain applications") rightImageSource: "qrc:/images/controls/chevron-right.svg" @@ -118,7 +119,7 @@ PageType { } } - DividerType {} + // DividerType {} } } } diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index b79d5d22..45f2dae9 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -46,12 +46,12 @@ PageType { QtObject { id: onlyForwardSites - property string name: qsTr("Only the addresses in the list must be opened via VPN") + property string name: qsTr("Addresses from the list should be accessed via VPN") property int type: routeMode.onlyForwardSites } QtObject { id: allExceptSites - property string name: qsTr("Addresses from the list should never be opened via VPN") + property string name: qsTr("Addresses from the list should not be accessed via VPN") property int type: routeMode.allExceptSites } @@ -81,7 +81,7 @@ PageType { Layout.fillWidth: true Layout.leftMargin: 16 - headerText: qsTr("Split site tunneling") + headerText: qsTr("Split tunneling") } SwitcherType { diff --git a/deploy/build_windows.bat b/deploy/build_windows.bat index c4b7b8cf..7ac37f14 100644 --- a/deploy/build_windows.bat +++ b/deploy/build_windows.bat @@ -47,7 +47,7 @@ cd %PROJECT_DIR% call "%QT_BIN_DIR:"=%\qt-cmake" . -B %WORK_DIR% cd %WORK_DIR% -cmake --build . --config release +cmake --build . --config release -- /p:UseMultiToolTask=true /m if %errorlevel% neq 0 exit /b %errorlevel% cmake --build . --target clean From a4624c7377c975e14957dc41b34345240d2d0c15 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 12 Oct 2023 10:57:13 +0500 Subject: [PATCH 212/278] removed the close button for the app for mobile platforms --- client/ui/qml/Pages2/PageSettings.qml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/ui/qml/Pages2/PageSettings.qml b/client/ui/qml/Pages2/PageSettings.qml index a806d472..387f5ffa 100644 --- a/client/ui/qml/Pages2/PageSettings.qml +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -109,6 +109,7 @@ PageType { DividerType {} LabelWithButtonType { + visible: GC.isMobile() Layout.fillWidth: true text: qsTr("Close application") @@ -120,7 +121,9 @@ PageType { } } - DividerType {} + DividerType { + visible: GC.isMobile() + } } } } From d1f66cbf4d64549a340d682b5f1d3da361160479 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Thu, 12 Oct 2023 16:26:37 +0800 Subject: [PATCH 213/278] updated translations for branch feature/amnezia-wireguard-client-impl --- client/translations/amneziavpn_ru.ts | 29 +- client/translations/amneziavpn_zh_CN.ts | 1061 +++++++++-------- client/ui/controllers/sitesController.cpp | 2 +- client/ui/qml/Pages2/PageProtocolRaw.qml | 2 +- .../ui/qml/Pages2/PageSettingsApplication.qml | 2 +- 5 files changed, 550 insertions(+), 546 deletions(-) diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index c46bebd9..ac099552 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -592,7 +592,7 @@ Already installed containers were found on the server. All installed containers - Connection options + Connection options %1 @@ -860,12 +860,12 @@ Already installed containers were found on the server. All installed containers - + About AmneziaVPN - + Close application @@ -977,12 +977,7 @@ Already installed containers were found on the server. All installed containers - Launch the application every time - - - - - starts + Launch the application every time %1 starts @@ -1149,21 +1144,21 @@ Already installed containers were found on the server. All installed containers - Split tunneling - - - - - App-based split tunneling + Site-based split tunneling - Allows you to choose which sites you want to use the VPN for. + Allows you to select which sites you want to access through the VPN + App-based split tunneling + + + + Allows you to use the VPN only for certain applications @@ -2651,7 +2646,7 @@ It's okay as long as it's from someone you trust. - The JSON data is not an array in file: + The JSON data is not an array in file: %1 diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index b4855a72..7a08682c 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -6,7 +6,7 @@ Split tunneling for WireGuard is not implemented, the option was disabled - 未启用选项,还未实现基于WireGuard协议的VPN分流 + 未启用选项,还未实现基于WireGuard协议的VPN分离 @@ -14,13 +14,13 @@ AmneziaVPN - + VPN Connected Refers to the app - which is currently running the background and waiting - VPN已连接 + VPN已连接 @@ -31,38 +31,38 @@ Connect - 连接 + 连接 VPN Protocols is not installed. Please install VPN container at first - 不存在VPN协议,请先安装 + 请先安装VPN协议 Connection... - 连接中 + 连接中 Connected - 已连接 + 已连接 Reconnection... - 重连中 + 重连中 Disconnection... - 断开中 + 断开中 Settings updated successfully, Reconnnection... - 配置已更新,重连中 + 配置已更新,重连中 @@ -70,17 +70,17 @@ Connection data - 连接数据 + 连接方式 Server IP, login and password - 服务器IP,用户名和密码 + 服务器IP,用户名和密码 QR code, key or configuration file - 二维码,授权码或者配置文件 + 二维码,授权码或者配置文件 @@ -88,22 +88,22 @@ C&ut - 剪切 + 剪切 &Copy - 拷贝 + 拷贝 &Paste - 粘贴 + 粘贴 &SelectAll - 全选 + 全选 @@ -111,7 +111,7 @@ Access error! - 访问错误 + 访问错误 @@ -119,12 +119,12 @@ The selected protocol is not supported on the current platform - 当前平台不支持所选协议 + 当前平台不支持所选协议 Reconnect via VPN Procotol: - 重连基于VPN协议: + 重连VPN基于协议: @@ -132,7 +132,7 @@ Scanned %1 of %2. - 扫描 %1 of %2. + 扫描 %1 of %2. @@ -149,46 +149,46 @@ %1 installed successfully. - %1 安装成功。 + %1 安装成功。 %1 is already installed on the server. - 服务器上已经安装 %1。 + 服务器上已经安装 %1。 Added containers that were already installed on the server - + 添加已安装在服务器上的容器 Already installed containers were found on the server. All installed containers have been added to the application - -在服务上发现已经安装协议并添加到应用程序 + +在服务上发现已经安装协议并添加至应用 Settings updated successfully - 配置更新成功 + 配置更新成功 Server '%1' was removed - 已移除服务器 '%1' + 已移除服务器 '%1' All containers from server '%1' have been removed - 服务器 '%1' 的所有容器已移除 + 服务器 '%1' 的所有容器已移除 %1 has been removed from the server '%2' - %1 已从服务器 '%2' 上移除 + %1 已从服务器 '%2' 上移除 1% has been removed from the server '%2' @@ -209,12 +209,12 @@ Already installed containers were found on the server. All installed containers Please login as the user - 请以用户身份登录 + 请以用户身份登录 Server added successfully - 服务器添加成功 + 增加服务器成功 @@ -222,17 +222,17 @@ Already installed containers were found on the server. All installed containers Read key failed: %1 - 获取授权码失败: %1 + 获取授权码失败: %1 Write key failed: %1 - 写入授权码失败: %1 + 写入授权码失败: %1 Delete key failed: %1 - 删除授权码失败: %1 + 删除授权码失败: %1 @@ -241,27 +241,27 @@ Already installed containers were found on the server. All installed containers AmneziaVPN - + VPN Connected - 已连接到VPN + 已连接到VPN VPN Disconnected - 已从VPN断开 + 已从VPN断开 AmneziaVPN notification - AmneziaVPN 提示 + AmneziaVPN 提示 Unsecured network detected: - 发现不安全网络 + 发现不安全网络 @@ -269,12 +269,12 @@ Already installed containers were found on the server. All installed containers Removing services from %1 - 正从 %1 移除服务 + 正从 %1 移除服务 Usually it takes no more than 5 minutes - 通常5分钟之内完成 + 大约5分钟之内完成 @@ -282,12 +282,12 @@ Already installed containers were found on the server. All installed containers VPN protocol - VPN协议 + VPN协议 Servers - 服务器 + 服务器 @@ -295,87 +295,87 @@ Already installed containers were found on the server. All installed containers AmneziaWG settings - + AmneziaWG 配置 Port - 端口 + 端口 Junk packet count - + 垃圾包数量 Junk packet minimum size - + 垃圾包最小值 Junk packet maximum size - + 垃圾包最大值 Init packet junk size - + 初始化垃圾包大小 Response packet junk size - + 响应垃圾包大小 Init packet magic header - + 初始化数据包魔数头 Response packet magic header - + 响应包魔数头 Transport packet magic header - + 传输包魔数头 Underload packet magic header - + 低负载数据包魔数头 Remove AmneziaWG - + 移除AmneziaWG Remove AmneziaWG from server? - + 从服务上移除AmneziaWG? All users who you shared a connection with will no longer be able to connect to it. - + 使用此共享连接的所有用户,将无法再连接它。 Continue - 继续 + 继续 Cancel - 取消 + 取消 Save and Restart Amnezia - 保存并重启Amnezia + 保存并重启Amnezia @@ -383,28 +383,28 @@ Already installed containers were found on the server. All installed containers Cloak settings - Cloak 配置 + Cloak 配置 Disguised as traffic from - 伪装流量来自 + 伪装流量为 Port - 端口 + 端口 Cipher - 解码 + 加密算法 Save and Restart Amnezia - 保存并重启Amnezia + 保存并重启Amnezia @@ -412,180 +412,180 @@ Already installed containers were found on the server. All installed containers OpenVPN settings - OpenVPN 配置 + OpenVPN 配置 VPN Addresses Subnet - VPN子网掩码 + VPN子网掩码 Network protocol - 网络协议 + 网络协议 Port - 端口 + 端口 Auto-negotiate encryption - 自动协商加密 + 自定义加密方式 Hash - + SHA512 - + SHA384 - + SHA256 - + SHA3-512 - + SHA3-384 - + SHA3-256 - + whirlpool - + BLAKE2b512 - + BLAKE2s256 - + SHA1 - + Cipher - 解码 + AES-256-GCM - + AES-192-GCM - + AES-128-GCM - + AES-256-CBC - + AES-192-CBC - + AES-128-CBC - + ChaCha20-Poly1305 - + ARIA-256-CBC - + CAMELLIA-256-CBC - + none - + TLS auth - TLS认证 + TLS认证 Block DNS requests outside of VPN - 阻止VPN外的DNS请求 + 阻止VPN外的DNS请求 Additional client configuration commands - 附加客户端配置命令 + 附加客户端配置命令 Commands: - 命令: + 命令: Additional server configuration commands - 附加服务器端配置命令 + 附加服务器端配置命令 Remove OpenVPN - 移除OpenVPN + 移除OpenVPN Remove OpenVpn from server? - 从服务器移除OpenVPN吗? + 从服务器移除OpenVPN吗? All users who you shared a connection with will no longer be able to connect to it. - + 使用此共享连接的所有用户,将无法再连接它。 All users with whom you shared a connection will no longer be able to connect to it @@ -594,17 +594,17 @@ Already installed containers were found on the server. All installed containers Continue - 继续 + 继续 Cancel - 取消 + 取消 Save and Restart Amnezia - 保存并重启Amnezia + 保存并重启Amnezia @@ -612,32 +612,36 @@ Already installed containers were found on the server. All installed containers settings - 配置 + 配置 Show connection options - 展示连接选项 + 显示连接选项 + + + Connection options + 连接选项 - Connection options - 连接选项 + Connection options %1 + %1 连接选项 Remove - 移除 + 移除 Remove %1 from server? - 从服务器移除 %1 ? + 从服务器移除 %1 ? All users who you shared a connection with will no longer be able to connect to it. - + 使用此共享连接的所有用户,将无法再连接它。 from server? @@ -650,12 +654,12 @@ Already installed containers were found on the server. All installed containers Continue - 继续 + 继续 Cancel - 取消 + 取消 @@ -663,23 +667,23 @@ Already installed containers were found on the server. All installed containers ShadowSocks settings - ShadowSocks 配置 + ShadowSocks 配置 Port - 端口 + 端口 Cipher - 解码 + 加密算法 Save and Restart Amnezia - 保存并重启Amnezia + 保存并重启Amnezia @@ -688,22 +692,23 @@ Already installed containers were found on the server. All installed containers A DNS service is installed on your server, and it is only accessible via VPN. - 您的服务器上安装了DNS服务,并且只能通过VPN访问。 + 您的服务器已安装DNS服务,仅能通过VPN访问。 + The DNS address is the same as the address of your server. You can configure DNS in the settings, under the connections tab. - DNS地址与您的服务器地址相同。您可以在连接选项卡下的设置中配置 DNS + 其地址与您的服务器地址相同。您可以在 设置 连接 中进行配置。 Remove - 移除 + 移除 Remove %1 from server? - 从服务器移除 %1 ? + 从服务器移除 %1 ? from server? @@ -712,12 +717,12 @@ Already installed containers were found on the server. All installed containers Continue - 继续 + 继续 Cancel - 取消 + 取消 @@ -725,17 +730,17 @@ Already installed containers were found on the server. All installed containers Settings updated successfully - 配置更新成功 + 配置更新成功 SFTP settings - SFTP 配置 + SFTP 配置 Host - 主机 + 主机 @@ -743,69 +748,69 @@ Already installed containers were found on the server. All installed containers Copied - 拷贝 + 拷贝 Port - 端口 + 端口 Login - 用户 + 用户 Password - 密码 + 密码 Mount folder on device - 在设备上挂载文件夹 + 挂载文件夹 In order to mount remote SFTP folder as local drive, perform following steps: <br> - 要将远程 SFTP 文件夹安装为本地驱动器,请执行以下步骤: <br> + 为将远程 SFTP 文件夹挂载到本地,请执行以下步骤: <br> <br>1. Install the latest version of - <br>1. 安装最新版的 + <br>1. 安装最新版的 <br>2. Install the latest version of - <br>2. 安装最新版的 + <br>2. 安装最新版的 Detailed instructions - 详细说明 + 详细说明 Remove SFTP and all data stored there - 移除SFTP和其本地所有数据 + 移除SFTP和其本地所有数据 Remove SFTP and all data stored there? - 移除SFTP和其本地所有数据? + 移除SFTP和其本地所有数据? Continue - 继续 + 继续 Cancel - 取消 + 取消 @@ -813,57 +818,57 @@ Already installed containers were found on the server. All installed containers Settings updated successfully - 配置更新成功 + 配置更新成功 Tor website settings - Tor网站配置 + Tor网站配置 Website address - 网址 + 网址 Copied - 拷贝 + 已拷贝 Use <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor Browser</a> to open this url. - 用 <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor 浏览器</a> 打开上面网址 + 用 <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor 浏览器</a> 打开上面网址 After installation it takes several minutes while your onion site will become available in the Tor Network. - 安装几分钟后,洋葱站点才会在 Tor 网络中生效。 + 完成安装几分钟后,洋葱站点才会在 Tor 网络中生效。 When configuring WordPress set the domain as this onion address. - 配置 WordPress 时,将域设置为此洋葱地址。 + 配置 WordPress 时,将域设置为此洋葱地址。 Remove website - 移除网站 + 移除网站 The site with all data will be removed from the tor network. - 网站及其所有数据将从 Tor 网络中删除 + 网站及其所有数据将从 Tor 网络中删除 Continue - 继续 + 继续 Cancel - 取消 + 取消 @@ -871,37 +876,37 @@ Already installed containers were found on the server. All installed containers Settings - 设置 + 设置 Servers - 服务器 + 服务器 Connection - 连接 + 连接 Application - 应用 + 应用 Backup - 备份 + 备份 - + About AmneziaVPN - 关于 + 关于 - + Close application - + 关闭应用 @@ -909,7 +914,7 @@ Already installed containers were found on the server. All installed containers Support the project with a donation - 捐款 + 捐款 This is a free and open source application. If you like it, support the developers with a donation. @@ -920,82 +925,83 @@ And if you don't like the app, all the more support it - the donation will This is a free and open source application. If you like it, support the developers with a donation. - + 这是一个免费且开源的软件。如果您喜欢它,请捐助开发者们。 + And if you don’t like the application, all the more reason to support it - the donation will be used for the improving the application. - + 如果您不喜欢,请捐助支持我们改进它。 Card on Patreon - Patreon订阅 + Patreon订阅 https://www.patreon.com/amneziavpn - + Show other methods on Github - 其他捐款途径 + 其他捐款途径 Contacts - 联系方式 + 联系方式 Telegram group - 电报群 + 电报群 To discuss features - 用于功能讨论 + 用于功能讨论 https://t.me/amnezia_vpn_en - + Mail - 邮件 + 邮件 For reviews and bug reports - 用于评论和提交软件的缺陷 + 用于评论和提交软件的缺陷 Github - + https://github.com/amnezia-vpn/amnezia-client - + Website - 官网 + 官网 https://amnezia.org - + Check for updates - 更新 + 检查更新 @@ -1003,82 +1009,85 @@ And if you don't like the app, all the more support it - the donation will Application - 应用 + 应用 Allow application screenshots - + 允许截屏 Auto start - 自动运行 + 自动运行 - Launch the application every time - 总是在系统 + 总是在系统 + + + starts + 启动时自动运行运用程序 - starts - 启动时自动运行运用程序 + Launch the application every time %1 starts + 运行应用软件在%1系统启动时 Start minimized - 最小化 + 最小化 Launch application minimized - 开启应用程序时窗口最小化 + 开启应用软件时窗口最小化 Language - 语言 + 语言 Logging - 日志 + 日志 Enabled - 开启 + 开启 Disabled - 禁用 + 禁用 Reset settings and remove all data from the application - 重置并清理应用的所有数据 + 重置并清理应用的所有数据 Reset settings and remove all data from the application? - 重置并清理应用的所有数据? + 重置并清理应用的所有数据? All settings will be reset to default. All installed AmneziaVPN services will still remain on the server. - 所有配置恢复为默认值。在服务器上保留所有已安装的AmneziaVPN服务。 + 所有配置恢复为默认值。服务器已安装的AmneziaVPN服务将被保留。 Continue - 继续 + 继续 Cancel - 取消 + 取消 @@ -1086,17 +1095,17 @@ And if you don't like the app, all the more support it - the donation will Settings restored from backup file - 从备份文件还原配置 + 从备份文件还原配置 Backup - 备份 + 备份 Configuration backup - 配置备份 + 备份设置 It will help you instantly restore connection settings at the next installation @@ -1105,53 +1114,53 @@ And if you don't like the app, all the more support it - the donation will You can save your settings to a backup file to restore them the next time you install the application. - + 您可以将配置信息备份到文件中,以便在下次安装应用软件时恢复配置 Make a backup - 进行备份 + 进行备份 Save backup file - 保存备份 + 保存备份 Backup files (*.backup) - + Restore from backup - 从备份还原 + 从备份还原 Open backup file - 打开备份文件 + 打开备份文件 Import settings from a backup file? - 从备份文件导入设置? + 从备份文件导入设置? All current settings will be reset - 当前所有设置将重置 + 当前所有设置将重置 Continue - 继续 + 继续 Cancel - 取消 + 取消 @@ -1159,17 +1168,17 @@ And if you don't like the app, all the more support it - the donation will Connection - 连接 + 连接 Auto connect - 自动连接 + 自动连接 Connect to VPN on app start - 应用开启时连接VPN + 应用开启时连接VPN Use AmneziaDNS if installed on the server @@ -1178,42 +1187,42 @@ And if you don't like the app, all the more support it - the donation will Use AmneziaDNS - 使用AmneziaDNS + 使用AmneziaDNS If AmneziaDNS is installed on the server - 如其已安装至服务器上 + 如果已在服务器安装AmneziaDNS DNS servers - DNS服务器列表 + DNS服务器 If AmneziaDNS is not used or installed - 如果未使用或未安装AmneziaDNS + 如果未使用或未安装AmneziaDNS - Split tunneling - + Site-based split tunneling + 基于网站的隧道分离 - + + Allows you to select which sites you want to access through the VPN + 配置想要通过VPN访问网站 + + + App-based split tunneling - + 基于应用的隧道分离 Split site tunneling 网站级VPN分流 - - - Allows you to choose which sites you want to use the VPN for. - - Allows you to connect to some sites through a secure connection, and to others bypassing it 使用VPN访问指定网站,其他的则绕过 @@ -1223,9 +1232,9 @@ And if you don't like the app, all the more support it - the donation will 应用级VPN分流 - + Allows you to use the VPN only for certain applications - 仅限指定应用使用VPN + 仅指定应用使用VPN @@ -1233,57 +1242,57 @@ And if you don't like the app, all the more support it - the donation will DNS servers - DNS服务器 + DNS服务器 If AmneziaDNS is not used or installed - 如果未使用或未安装Amnezia DNS + 如果未使用或未安装AmneziaDNS Primary DNS - 首选 DNS + 首选 DNS Secondary DNS - 备用 DNS + 备用 DNS Restore default - 恢复默认配置 + 恢复默认配置 Restore default DNS settings? - 是否恢复默认DNS配置? + 是否恢复默认DNS配置? Continue - 继续 + 继续 Cancel - 取消 + 取消 Settings have been reset - 已重置 + 已重置 Save - 保存 + 保存 Settings saved - 配置已保存 + 配置已保存 @@ -1291,57 +1300,57 @@ And if you don't like the app, all the more support it - the donation will Logging - 日志 + 日志 Save logs - 记录日志 + 记录日志 Open folder with logs - 打开日志文件夹 + 打开日志文件夹 Save - 保存 + 保存 Logs files (*.log) - + Save logs to file - 保存日志到文件 + 保存日志到文件 Clear logs? - 清除日志? + 清理日志? Continue - 继续 + 继续 Cancel - 取消 + 取消 Logs have been cleaned up - 已清理日志 + 日志已清理 Clear logs - 清理日志 + 清理日志 @@ -1349,27 +1358,27 @@ And if you don't like the app, all the more support it - the donation will All installed containers have been added to the application - 所有已安装的容器已添加到应用程序中 + 所有已安装的容器,已被添加到应用软件 No new installed containers found - 未找到新安装的容器 + 未发现新安装的容器 Clear Amnezia cache - 清除 Amnezia 缓存 + 清除 Amnezia 缓存 May be needed when changing other settings - 更改其他设置时可能需要 + 更改其他设置时可能需要缓存 Clear cached profiles? - 清除缓存的配置文件? + 清除缓存? @@ -1381,54 +1390,54 @@ And if you don't like the app, all the more support it - the donation will Continue - 继续 + 继续 Cancel - 取消 + 取消 Check the server for previously installed Amnezia services - 检查服务器上是否存在 Amnezia 服务 + 检查服务器上,是否存在之前安装的 Amnezia 服务 Add them to the application if they were not displayed - 如果存在且未被显示,则添加到应用程序里 + 如果存在且未显示,则添加到应用软件 Remove server from application - 移除本地服务器信息 + 移除本地服务器信息 Remove server? - 移除本地服务器信息? + 移除本地服务器信息? All installed AmneziaVPN services will still remain on the server. - 所有已安装的 AmneziaVPN 服务仍将保留在服务器上。 + 所有已安装的 AmneziaVPN 服务仍将保留在服务器上。 Clear server from Amnezia software - 移除Amnezia中服务器信息 + 清理Amnezia中服务器信息 Clear server from Amnezia software? - 从Amnezia中清除服务器? + 清理Amnezia中服务器信息 All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted. - 服务器上的所有容器都将被删除。这意味着配置文件、密钥和证书将被删除。 + 服务器上的所有容器都将被删除。配置文件、密钥和证书也将被删除。 @@ -1436,27 +1445,27 @@ And if you don't like the app, all the more support it - the donation will Server name - 服务器名称 + 服务器名 Save - 保存 + 保存 Protocols - 协议 + 协议 Services - 服务 + 服务 Data - 数据 + 数据 @@ -1464,17 +1473,17 @@ And if you don't like the app, all the more support it - the donation will settings - 配置 + 配置 Remove - 移除 + 移除 All users who you shared a connection with will no longer be able to connect to it. - + 使用此共享连接的所有用户,将无法再连接它。 from server? @@ -1483,7 +1492,7 @@ And if you don't like the app, all the more support it - the donation will Remove %1 from server? - 从服务器移除 %1 ? + 从服务器移除 %1 ? All users with whom you shared a connection will no longer be able to connect to it @@ -1492,12 +1501,12 @@ And if you don't like the app, all the more support it - the donation will Continue - 继续 + 继续 Cancel - 取消 + 取消 @@ -1505,7 +1514,7 @@ And if you don't like the app, all the more support it - the donation will Servers - 服务器 + 服务器 @@ -1525,90 +1534,90 @@ And if you don't like the app, all the more support it - the donation will Addresses from the list should be accessed via VPN - + 仅使用VPN访问 Addresses from the list should not be accessed via VPN - + 不使用VPN访问 Split tunneling - + 隧道分离 Mode - 方式 + 规则 Remove - 移除 + 移除 Continue - 继续 + 继续 Cancel - 取消 + 取消 Site or IP - 网址或IP地址 + 网站或IP地址 Import/Export Sites - 导入/导出网址 + 导入/导出网站 Import - 导入 + 导入 Save site list - 保存网址 + 保存网址 Save sites - 保存网址 + 保存网址 Sites files (*.json) - + Import a list of sites - 导入网址列表 + 导入网址列表 Replace site list - 替换网址列表 + 替换网址列表 Open sites file - 打开网址文件 + 打开网址文件 Add imported sites to existing ones - 将导入的网址添加到现有网址中 + 将导入的网址添加到现有网址中 @@ -1616,45 +1625,45 @@ And if you don't like the app, all the more support it - the donation will Server connection - 服务器连接 + 服务器连接 Do not use connection code from public sources. It may have been created to intercept your data. It's okay as long as it's from someone you trust. - 请勿使用公共来源的连接代码。它可能是为了拦截您的数据而创建的。 -最好是来源可信。 + 请勿使用公共来源的连接码。它可能是为了拦截您的数据而创建的。 +请确保连接码来源可信。 What do you have? - + 你用什么方式创建连接? File with connection settings or backup - 包含连接配置或备份的文件 + 包含连接配置或备份的文件 File with connection settings - 包含连接配置的文件 + 包含连接配置的文件 Open config file - 打开配置文件 + 打开配置文件 QR-code - 二维码 + 二维码 Key as text - 授权码文本 + 授权码文本 @@ -1662,52 +1671,52 @@ It's okay as long as it's from someone you trust. Server connection - 服务器连接 + 连接服务器 Server IP address [:port] - 服务器IP [:端口] + 服务器IP [:端口] 255.255.255.255:88 - + Login to connect via SSH - 用户名 + ssh账号 Password / SSH private key - 密码 或者 私钥 + 密码 或 私钥 Continue - 继续 + 继续 Ip address cannot be empty - IP不能为空 + IP不能为空 Enter the address in the format 255.255.255.255:88 - 按照这种格式输入 255.255.255.255:88 + 按照这种格式输入 255.255.255.255:88 Login cannot be empty - 用户名不能为空 + 账号不能为空 Password/private key cannot be empty - 密码或者私钥不能为空 + 密码或私钥不能为空 @@ -1715,27 +1724,27 @@ It's okay as long as it's from someone you trust. What is the level of internet control in your region? - 您所在地区的互联网控制力度如何? + 您所在地区的互联网管控力度如何? Set up a VPN yourself - 自己架设VPN + 自己架设VPN I want to choose a VPN protocol - 我想选择VPN协议 + 我想选择VPN协议 Continue - 继续 + 继续 Set up later - 稍后设置 + 稍后设置 @@ -1744,32 +1753,32 @@ It's okay as long as it's from someone you trust. Usually it takes no more than 5 minutes - 通常不超过5分钟 + 通常不超过5分钟 The server has already been added to the application - 服务器已添加到应用程序中 + 服务器已添加到应用软件中 Amnesia has detected that your server is currently - Amnezia 检测到您的服务器当前 + Amnezia 检测到您的服务器当前 busy installing other software. Amnesia installation - 正安装其他软件。Amnezia安装 + 正安装其他软件。Amnezia安装 will pause until the server finishes installing other software - 将暂停,直到服务器完成安装其他软件。 + 将暂停,直到其他软件安装完成。 Installing - 安装中 + 安装中 @@ -1777,32 +1786,32 @@ It's okay as long as it's from someone you trust. Installing %1 - 正在安装 %1 + 正在安装 %1 More detailed - 更多细节 + 更多细节 Close - 关闭 + 关闭 Network protocol - 网络协议 + 网络协议 Port - 端口 + 端口 Install - 安装 + 安装 @@ -1810,12 +1819,12 @@ It's okay as long as it's from someone you trust. VPN protocol - VPN 协议 + VPN 协议 Choose the one with the highest priority for you. Later, you can install other protocols and additional services, such as DNS proxy and SFTP. - 选择最适合您的一项。稍后,您可以安装其他协议和附加服务,例如 DNS 代理和 SFTP。 + 选择你认为优先级最高的一项。稍后,您可以安装其他协议和附加服务,例如 DNS 代理和 SFTP。 @@ -1823,7 +1832,7 @@ It's okay as long as it's from someone you trust. Point the camera at the QR code and hold for a couple of seconds. - 将相机对准二维码并按住几秒钟 + 将相机对准二维码并按住几秒钟 @@ -1831,27 +1840,27 @@ It's okay as long as it's from someone you trust. Settings restored from backup file - 从备份文件还原配置 + 从备份文件还原配置 Free service for creating a personal VPN on your server. - + 在您的服务器上架设私人免费VPN服务。 Helps you access blocked content without revealing your privacy, even to VPN providers. - + 帮助您访问受限内容,保护您的隐私,即使是VPN提供商也无法获取。 I have the data to connect - + 我有连接配置 I have nothing - + 我没有 @@ -1859,27 +1868,27 @@ It's okay as long as it's from someone you trust. Connection key - 连接授权码 + 连接授权码 A line that starts with vpn://... - 以 vpn://... 开始的行 + 以 vpn://... 开始的行 Key - 授权码 + 授权码 Insert - 插入 + 插入 Continue - 继续 + 继续 @@ -1887,27 +1896,27 @@ It's okay as long as it's from someone you trust. New connection - 新连接 + 新连接 Do not use connection code from public sources. It could be created to intercept your data. - 请勿使用公共来源的连接代码。它可以被创建来拦截您的数据。 + 请勿使用公共来源的连接码。它可以被创建来拦截您的数据。 Collapse content - + 折叠内容 Show content - 展示内容 + 显示内容 Connect - 连接 + 连接 @@ -1915,52 +1924,52 @@ It's okay as long as it's from someone you trust. Save OpenVPN config - 保存OpenVPN配置 + 保存OpenVPN配置 Save WireGuard config - 保存WireGuard配置 + 保存WireGuard配置 For the AmneziaVPN app - AmneziaVPN 应用 + AmneziaVPN 应用 OpenVpn native format - OpenVPN原生格式 + OpenVPN原生格式 WireGuard native format - WireGuard原生格式 + WireGuard原生格式 VPN Access - 访问VPN + 访问VPN Connection - 连接 + 连接 Full access - 完整授权 + 完全访问 VPN access without the ability to manage the server - 无权控制服务器 + 访问VPN,但没有权限管理服务。 Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the servers, as well as change settings. - + 除访问VPN外,用户还能添加和删除协议、服务以及更改配置信息 Full access to server @@ -1974,22 +1983,22 @@ It's okay as long as it's from someone you trust. Server - 服务器 + 服务器 Accessing - 访问 + 访问 File with accessing settings to - + 访问配置文件的内容为: File with connection settings to - 连接配置文件的内容为: + 连接配置文件的内容为: Protocols @@ -1999,23 +2008,23 @@ It's okay as long as it's from someone you trust. Protocol - 协议 + 协议 Connection to - 连接到 + 连接到 Connection format - 连接方式 + 连接格式 Share - 共享 + 共享 @@ -2023,7 +2032,7 @@ It's okay as long as it's from someone you trust. Close - 关闭 + 关闭 @@ -2031,38 +2040,38 @@ It's okay as long as it's from someone you trust. Password entry not found - 没有密码输入 + 未发现秘密 Could not decrypt data - 不能加密数据 + 数据无法加密 Unknown error - 位置错误 + 未知错误 Could not open wallet: %1; %2 - 无法打开钱包: %1; %2 + 无法打开钱包: %1; %2 Password not found - 未发现密码 + 未发现密码 Could not open keystore - 无法打开密钥库 + 无法打开密钥库 Could not remove private key from keystore - 无法从密钥库中删除私钥 + 无法从密钥库中删除私钥 @@ -2070,12 +2079,12 @@ It's okay as long as it's from someone you trust. Unknown error - 未知错误 + 未知错误 Access to keychain denied - 访问钥匙串被拒绝 + 访问钥匙串被拒绝 @@ -2083,27 +2092,27 @@ It's okay as long as it's from someone you trust. Could not store data in settings: access error - 无法在配置中存储数据:访问错误 + 无法在配置中存储数据:访问错误 Could not store data in settings: format error - 无法在陪置中存储数据:格式错误 + 无法在陪置中存储数据:格式错误 Could not delete data from settings: access error - 无法在配置中删除数据:访问错误 + 无法在配置中删除数据:访问错误 Could not delete data from settings: format error - 无法在配置中删除数据:格式错误 + 无法在配置中删除数据:格式错误 Entry not found - 未找到条目 + 未找到条目 @@ -2111,80 +2120,80 @@ It's okay as long as it's from someone you trust. Password entry not found - 没有密码输入 + 未发现密码 Could not decrypt data - 不能加密数据 + 数据无法加密 D-Bus is not running - + D-Bus未运行 Unknown error - + 未知错误 No keychain service available - + 没有有效的钥匙串服务 Could not open wallet: %1; %2 - 无法打开钱包: %1; %2 + 无法打开钱包: %1; %2 Access to keychain denied - 访问钥匙串被拒绝 + 访问钥匙串被拒绝 Could not determine data type: %1; %2 - + 无法确定数据类型: %1; %2 Entry not found - + 未找到记录 Unsupported entry type 'Map' - + 不支持的记录类型 'Map' Unknown kwallet entry type '%1' - + 未知钱包类型 '%1' Password not found - 未发现密码 + 未发现密码 Could not open keystore - 无法打开密钥库 + 无法打开密钥库 Could not retrieve private key from keystore - 无法从密钥存储库中检索私钥 + 无法从密钥存储库中检索私钥 Could not create decryption cipher - 无法创建解密密码 + 无法创建解密算法 @@ -2192,73 +2201,73 @@ It's okay as long as it's from someone you trust. Credential size exceeds maximum size of %1 - + 证书大小超过上限,最大为: %1 Credential key exceeds maximum size of %1 - + 凭证密钥大小超过上限,最大为: %1 Writing credentials failed: Win32 error code %1 - + 写入凭证失败,Win32错误码: %1 Encryption failed - + 加密失败 D-Bus is not running - + D-Bus未运行 Unknown error - + 未知错误 Could not open wallet: %1; %2 - 无法打开钱包: %1; %2 + 无法打开钱包: %1; %2 Password not found - 未发现密码 + 未发现密码 Could not open keystore - 无法打开密钥库 + 无法打开密钥库 Could not create private key generator - 无法创建私钥生成器 + 无法创建私钥生成器 Could not generate new private key - 无法生成新的私钥 + 无法生成新的私钥 Could not retrieve private key from keystore - 无法从密钥库检索私钥 + 无法从密钥库检索私钥 Could not create encryption cipher - 无法创建加密密码 + 无法创建加密密码 Could not encrypt data - 无法加密数据 + 无法加密数据 @@ -2266,374 +2275,374 @@ It's okay as long as it's from someone you trust. Sftp service - Sftp 服务 + Sftp 服务 No error - 没有错误 + 没有错误 Unknown Error - 位置错误 + 未知错误 Function not implemented - 功能未实现 + 功能未实现 Server check failed - 服务器检测失败 + 服务器检测失败 Server port already used. Check for another software - 检测服务器该端口是否被其他软件被占用 + 检测服务器该端口是否被其他软件被占用 Server error: Docker container missing - Server error: Docker容器丢失 + 服务器错误: Docker容器丢失 Server error: Docker failed - Server error: Docker失败 + 服务器错误: Docker失败 Installation canceled by user - 用户取消安装 + 用户取消安装 The user does not have permission to use sudo - 用户没有root权限 + 用户没有root权限 Ssh request was denied - ssh请求被拒绝 + ssh请求被拒绝 Ssh request was interrupted - ssh请求中断 + ssh请求中断 Ssh internal error - ssh内部错误 + ssh内部错误 Invalid private key or invalid passphrase entered - 输入的私钥或密码无效 + 输入的私钥或密码无效 The selected private key format is not supported, use openssh ED25519 key types or PEM key types - 不支持所选私钥格式,请使用 openssh ED25519 密钥类型或 PEM 密钥类型 + 不支持所选私钥格式,请使用 openssh ED25519 密钥类型或 PEM 密钥类型 Timeout connecting to server - 连接服务器超时 + 连接服务器超时 Sftp error: End-of-file encountered - Sftp错误: 遇到文件结尾 + Sftp错误: End-of-file encountered Sftp error: File does not exist - Sftp错误: 文件不存在 + Sftp错误: 文件不存在 Sftp error: Permission denied - Sftp错误: 权限受限 + Sftp错误: 权限不足 Sftp error: Generic failure - Sftp错误: 一般失败 + Sftp错误: 一般失败 Sftp error: Garbage received from server - Sftp错误: 从服务器收到垃圾信息 + Sftp错误: 从服务器收到垃圾信息 Sftp error: No connection has been set up - + Sftp 错误: 未建立连接 Sftp error: There was a connection, but we lost it - + Sftp 错误: 已有连接丢失 Sftp error: Operation not supported by libssh yet - + Sftp error: libssh不支持该操作 Sftp error: Invalid file handle - + Sftp error: 无效的文件句柄 Sftp error: No such file or directory path exists - + Sftp 错误: 文件夹或文件不存在 Sftp error: An attempt to create an already existing file or directory has been made - + Sftp 错误: 文件或目录已存在 Sftp error: Write-protected filesystem - + Sftp 错误: 文件系统写保护 Sftp error: No media was in remote drive - + Sftp 错误: 远程驱动器中没有媒介 Failed to save config to disk - 配置保存到磁盘失败 + 配置保存到磁盘失败 OpenVPN config missing - OpenVPN配置丢失 + OpenVPN配置丢失 OpenVPN management server error - OpenVPN 管理服务器错误 + OpenVPN 管理服务器错误 OpenVPN executable missing - OpenVPN 可执行文件丢失 + OpenVPN 可执行文件丢失 ShadowSocks (ss-local) executable missing - ShadowSocks (ss-local) 执行文件丢失 + ShadowSocks (ss-local) 执行文件丢失 Cloak (ck-client) executable missing - Cloak (ck-client) 执行文件丢失 + Cloak (ck-client) 执行文件丢失 Amnezia helper service error - Amnezia 帮助服务错误 + Amnezia 服务连接失败 OpenSSL failed - OpenSSL失败 + OpenSSL错误 Can't connect: another VPN connection is active - 无法连接:另一个VPN连接处于活动状态 + 无法连接:另一个VPN连接处于活跃状态 Can't setup OpenVPN TAP network adapter - 无法设置 OpenVPN TAP 网络适配器 + 无法设置 OpenVPN TAP 网络适配器 VPN pool error: no available addresses - VPN 池错误:没有可用地址 + VPN 池错误:没有可用地址 The config does not contain any containers and credentiaks for connecting to the server - 该配置不包含任何用于连接到服务器的容器和凭据。 + 该配置不包含任何用于连接到服务器的容器和凭据。 Internal error - 内部错误 + 内部错误 IPsec - + Website in Tor network - 在 Tor 网络中架设网站 + 在 Tor 网络中架设网站 Amnezia DNS - + Sftp file sharing service - SFTP文件共享服务 + SFTP文件共享服务 OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its own security protocol with SSL/TLS for key exchange. - OpenVPN 是最流行的 VPN 协议,具有灵活的配置选项。它使用自己的安全协议与 SSL/TLS 进行密钥交换。 + OpenVPN 是最流行的 VPN 协议,具有灵活的配置选项。它使用自己的安全协议与 SSL/TLS 进行密钥交换。 ShadowSocks - masks VPN traffic, making it similar to normal web traffic, but is recognised by analysis systems in some highly censored regions. - ShadowSocks - 混淆 VPN 流量,使其与正常的 Web 流量相似,但在一些审查力度高的地区可以被分析系统识别。 + ShadowSocks - 混淆 VPN 流量,使其与正常的 Web 流量相似,但在一些审查力度高的地区可以被分析系统识别。 OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against active-probbing detection. Ideal for bypassing blocking in regions with the highest levels of censorship. - OpenVPN over Cloak - OpenVPN 与 VPN 具有伪装成网络流量和防止主动探测检测的保护。非常适合绕过审查力度特别强的地区的封锁。 + OpenVPN over Cloak - OpenVPN 与 VPN 具有伪装成网络流量和防止主动探测检测的保护。非常适合绕过审查力度特别强的地区的封锁。 WireGuard - New popular VPN protocol with high performance, high speed and low power consumption. Recommended for regions with low levels of censorship. - WireGuard - 新型流行的VPN协议,具有高性能、高速度和低功耗。建议用于审查力度较低的地区 + WireGuard - 新型流行的VPN协议,具有高性能、高速度和低功耗。建议用于审查力度较低的地区 IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS. - IKEv2 - 现代稳定协议,相比其他协议较快一些,在信号丢失后恢复连接。Android 和 iOS最新版原生支持。 + IKEv2 - 现代稳定协议,相比其他协议较快一些,在信号丢失后恢复连接。Android 和 iOS最新版原生支持。 Deploy a WordPress site on the Tor network in two clicks. - 只需点击两次即可架设 WordPress 网站到 Tor 网络 + 只需点击两次即可架设 WordPress 网站到 Tor 网络 Replace the current DNS server with your own. This will increase your privacy level. - 将当前的 DNS 服务器替换为您自己的。这将提高您的隐私级别。 + 将当前的 DNS 服务器替换为您自己的。这将提高您的隐私保护级别。 Creates a file vault on your server to securely store and transfer files. - 在您的服务器上创建文件库以安全地存储和传输文件 + 在您的服务器上创建文件仓库,以便安全地存储和传输文件 OpenVPN container - OpenVPN容器 + OpenVPN容器 Container with OpenVpn and ShadowSocks - 带有 OpenVpn 和 ShadowSocks 的容器 + 含 OpenVpn 和 ShadowSocks 的容器 Container with OpenVpn and ShadowSocks protocols configured with traffic masking by Cloak plugin - 具有 OpenVpn 和 ShadowSocks 协议的容器,通过 Cloak 插件配置混淆流量 + 含 OpenVpn 和 ShadowSocks 协议的容器,通过 Cloak 插件配置混淆流量 WireGuard container - WireGuard 容器 + WireGuard 容器 AmneziaWG container - + IPsec container - IPsec 容器 + IPsec 容器 DNS Service - DNS 服务 + DNS 服务 Sftp file sharing service - is secure FTP service - Sftp 文件共享服务 - 安全的 FTP 服务 + Sftp 文件共享服务 - 安全的 FTP 服务 Entry not found - 未找到记录 + 未找到记录 Access to keychain denied - 访问钥匙串被拒绝 + 访问钥匙串被拒绝 No keyring daemon - 没有密钥环守护进程 + 没有密钥环守护进程 Already unlocked - 已经解锁 + 已经解锁 No such keyring - 没有这样的密钥环 + 没有这样的密钥环 Bad arguments - 错误参数 + 错误参数 I/O error - I/O错误 + I/O错误 Cancelled - 已取消 + 已取消 Keyring already exists - 密匙环已经存在 + 密匙环已经存在 No match - 不匹配 + 不匹配 Unknown error - 未知错误 + 未知错误 error 0x%1: %2 - 错误 0x%1: %2 + 错误 0x%1: %2 @@ -2641,7 +2650,7 @@ It's okay as long as it's from someone you trust. Choose language - 选择语言 + 选择语言 @@ -2649,13 +2658,13 @@ It's okay as long as it's from someone you trust. Server #1 - + Server - 服务器 + 服务器 @@ -2663,22 +2672,22 @@ It's okay as long as it's from someone you trust. Software version - 软件版本 + 软件版本 Backup file is corrupted - 备份文件已损坏 + 备份文件已损坏 All settings have been reset to default values - 所配置恢复为默认值 + 所配置恢复为默认值 Cached profiles cleared - 缓存的配置文件已清除 + 缓存的配置文件已清除 @@ -2687,27 +2696,27 @@ It's okay as long as it's from someone you trust. Save AmneziaVPN config - 保存配置 + 保存配置 Share - 共享 + 共享 Copy - 拷贝 + 拷贝 Copied - 已拷贝 + 已拷贝 Show connection settings - + 显示连接配置 Show content @@ -2716,7 +2725,7 @@ It's okay as long as it's from someone you trust. To read the QR code in the Amnezia app, select "Add server" → "I have data to connect" → "QR code, key or settings file" - 要读取 Amnezia 应用程序中的二维码,请选择“添加服务器”→“我有数据要连接”→“二维码、密钥或配置文件” + 要应用二维码到 Amnezia,请底部工具栏点击“+”→“连接方式”→“二维码、授权码或配置文件” @@ -2724,42 +2733,42 @@ It's okay as long as it's from someone you trust. Hostname not look like ip adress or domain name - + 请输入有效的域名或IP地址 New site added: %1 - + 已经添加新网站: %1 Site removed: %1 - + 已移除网站: %1 Can't open file: %1 - + 无法打开文件: %1 Failed to parse JSON data from file: %1 - + JSON解析失败,文件: %1 - The JSON data is not an array in file: - + The JSON data is not an array in file: %1 + 文件中的JSON数据不是一个数组,文件: %1 Import completed - + 完成导入 Export completed - + 完成导出 @@ -2768,31 +2777,31 @@ It's okay as long as it's from someone you trust. Show - 界面 + 显示 Connect - 连接 + 连接 Disconnect - 断开 + 断开 Visit Website - 官网 + 官网 Quit - 退出 + 退出 @@ -2800,7 +2809,7 @@ It's okay as long as it's from someone you trust. The field can't be empty - + 输入不能为空 @@ -2808,7 +2817,7 @@ It's okay as long as it's from someone you trust. Mbps - + @@ -2816,42 +2825,42 @@ It's okay as long as it's from someone you trust. Unknown - 未知 + 未知 Disconnected - 断开连接 + 连接已断开 Preparing - 准备中 + 准备中 Connecting... - 连接中 + 连接中 Connected - 已连接 + 已连接 Disconnecting... - 断开中 + 断开中 Reconnecting... - 重连中 + 重连中 Error - 错误 + 错误 @@ -2859,32 +2868,32 @@ It's okay as long as it's from someone you trust. Low - + High - + Medium - + I just want to increase the level of privacy - 我只是想提高隐私级别 + 我只是想提高隐私保护级别 Many foreign websites and VPN providers are blocked - 大多国外网站和VPN提供商被屏蔽 + 大多国外网站和VPN提供商被屏蔽 Some foreign sites are blocked, but VPN providers are not blocked - 一些国外网站被屏蔽,但VPN提供商未被屏蔽 + 一些国外网站被屏蔽,但VPN提供商未被屏蔽 @@ -2892,12 +2901,12 @@ It's okay as long as it's from someone you trust. Private key passphrase - 私钥密码 + 私钥密码 Save - 保存 + 保存 diff --git a/client/ui/controllers/sitesController.cpp b/client/ui/controllers/sitesController.cpp index 4d0391be..8c420899 100644 --- a/client/ui/controllers/sitesController.cpp +++ b/client/ui/controllers/sitesController.cpp @@ -97,7 +97,7 @@ void SitesController::importSites(const QString &fileName, bool replaceExisting) } if (!jsonDocument.isArray()) { - emit errorOccurred(tr("The JSON data is not an array in file: ").arg(fileName)); + emit errorOccurred(tr("The JSON data is not an array in file: %1").arg(fileName)); return; } diff --git a/client/ui/qml/Pages2/PageProtocolRaw.qml b/client/ui/qml/Pages2/PageProtocolRaw.qml index 2324c091..f0959143 100644 --- a/client/ui/qml/Pages2/PageProtocolRaw.qml +++ b/client/ui/qml/Pages2/PageProtocolRaw.qml @@ -127,7 +127,7 @@ PageType { Layout.fillWidth: true Layout.topMargin: 16 - headerText: qsTr("Connection options ") + protocolName + headerText: qsTr("Connection options %1").arg(protocolName) } TextArea { diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index c5536fdb..49e3a5d9 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -70,7 +70,7 @@ PageType { Layout.margins: 16 text: qsTr("Auto start") - descriptionText: qsTr("Launch the application every time ") + Qt.platform.os + qsTr(" starts") + descriptionText: qsTr("Launch the application every time %1 starts").arg(Qt.platform.os) checked: SettingsController.isAutoStartEnabled() onCheckedChanged: { From 7c8399ce88d92fa17c1a05b8b3926f1ef0821280 Mon Sep 17 00:00:00 2001 From: pokamest Date: Thu, 12 Oct 2023 13:57:58 +0100 Subject: [PATCH 214/278] Fix awg description --- client/containers/containers_defs.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index fd13bfe0..2fcff6a7 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -109,8 +109,8 @@ QMap ContainerProps::containerDescriptions() QObject::tr("WireGuard - New popular VPN protocol with high performance, high speed and low power " "consumption. Recommended for regions with low levels of censorship.") }, { DockerContainer::Awg, - QObject::tr("WireGuard - New popular VPN protocol with high performance, high speed and low power " - "consumption. Recommended for regions with low levels of censorship.") }, + QObject::tr("AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, but very resistant to blockages. " + "Recommended for regions with high levels of censorship.") }, { DockerContainer::Ipsec, QObject::tr("IKEv2 - Modern stable protocol, a bit faster than others, restores connection after " "signal loss. It has native support on the latest versions of Android and iOS.") }, From 3836836c72645797fb0f1896555fcde5e8087bc2 Mon Sep 17 00:00:00 2001 From: pokamest Date: Sat, 14 Oct 2023 02:15:49 +0100 Subject: [PATCH 215/278] Change easySetupOrder --- client/containers/containers_defs.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index 2fcff6a7..4bd3b0ef 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -233,8 +233,8 @@ bool ContainerProps::isEasySetupContainer(DockerContainer container) { switch (container) { case DockerContainer::WireGuard: return true; + case DockerContainer::Awg: return true; case DockerContainer::Cloak: return true; - case DockerContainer::OpenVpn: return true; default: return false; } } @@ -243,8 +243,8 @@ QString ContainerProps::easySetupHeader(DockerContainer container) { switch (container) { case DockerContainer::WireGuard: return tr("Low"); - case DockerContainer::Cloak: return tr("High"); - case DockerContainer::OpenVpn: return tr("Medium"); + case DockerContainer::Awg: return tr("Medium or High"); + case DockerContainer::Cloak: return tr("Extreme"); default: return ""; } } @@ -252,9 +252,9 @@ QString ContainerProps::easySetupHeader(DockerContainer container) QString ContainerProps::easySetupDescription(DockerContainer container) { switch (container) { - case DockerContainer::WireGuard: return tr("I just want to increase the level of privacy"); - case DockerContainer::Cloak: return tr("Many foreign websites and VPN providers are blocked"); - case DockerContainer::OpenVpn: return tr("Some foreign sites are blocked, but VPN providers are not blocked"); + case DockerContainer::WireGuard: return tr("I just want to increase the level of my privacy."); + case DockerContainer::Awg: return tr("I want to bypass censorship. This option recommended in most cases."); + case DockerContainer::Cloak: return tr("Most VPN protocols are blocked. Recommended if other options are not working."); default: return ""; } } @@ -262,9 +262,9 @@ QString ContainerProps::easySetupDescription(DockerContainer container) int ContainerProps::easySetupOrder(DockerContainer container) { switch (container) { - case DockerContainer::WireGuard: return 1; - case DockerContainer::Cloak: return 3; - case DockerContainer::OpenVpn: return 2; + case DockerContainer::WireGuard: return 3; + case DockerContainer::Awg: return 2; + case DockerContainer::Cloak: return 1; default: return 0; } } From 8163e5143495f0856e11178e60507d84240cef46 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sat, 14 Oct 2023 16:52:22 +0500 Subject: [PATCH 216/278] fixes on page split tunneling according to the design layout --- client/translations/amneziavpn_ru.ts | 4 +-- client/translations/amneziavpn_zh_CN.ts | 4 +-- .../ui/qml/Controls2/LabelWithButtonType.qml | 21 +++++++++++++--- .../ui/qml/Pages2/PageSettingsConnection.qml | 12 +++++++-- .../qml/Pages2/PageSettingsSplitTunneling.qml | 25 ++++++++++++++++--- 5 files changed, 53 insertions(+), 13 deletions(-) diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index 0e1d65df..153c2569 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -130,7 +130,7 @@ ImportController - + Scanned %1 of %2. @@ -777,7 +777,7 @@ Already installed containers were found on the server. All installed containers - + Close application diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index 645d281f..4f566201 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -130,7 +130,7 @@ ImportController - + Scanned %1 of %2. 扫描 %1 of %2. @@ -811,7 +811,7 @@ Already installed containers were found on the server. All installed containers 关于 - + Close application diff --git a/client/ui/qml/Controls2/LabelWithButtonType.qml b/client/ui/qml/Controls2/LabelWithButtonType.qml index 6bc45a8c..8b85d591 100644 --- a/client/ui/qml/Controls2/LabelWithButtonType.qml +++ b/client/ui/qml/Controls2/LabelWithButtonType.qml @@ -20,7 +20,9 @@ Item { property bool isLeftImageHoverEnabled: true //todo separete this qml file to 3 property string textColor: "#d7d8db" + property string textDisabledColor: "#878B91" property string descriptionColor: "#878B91" + property string descriptionDisabledColor: "#494B50" property real textOpacity: 1.0 property string rightImageColor: "#d7d8db" @@ -71,7 +73,14 @@ Item { ListItemTitleType { text: root.text - color: root.descriptionOnTop ? root.descriptionColor : root.textColor + color: { + if (root.enabled) { + return root.descriptionOnTop ? root.descriptionColor : root.textColor + } else { + return root.descriptionOnTop ? root.descriptionDisabledColor : root.textDisabledColor + } + } + maximumLineCount: root.textMaximumLineCount elide: root.textElide @@ -96,7 +105,13 @@ Item { id: description text: root.descriptionText - color: root.descriptionOnTop ? root.textColor : root.descriptionColor + color: { + if (root.enabled) { + return root.descriptionOnTop ? root.textColor : root.descriptionColor + } else { + return root.descriptionOnTop ? root.textDisabledColor : root.descriptionDisabledColor + } + } opacity: root.textOpacity @@ -157,7 +172,7 @@ Item { MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor - hoverEnabled: true + hoverEnabled: root.enabled onEntered: { if (rightImageSource) { diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml index 374e1ce4..76dbdff6 100644 --- a/client/ui/qml/Pages2/PageSettingsConnection.qml +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -94,6 +94,8 @@ PageType { DividerType {} LabelWithButtonType { + visible: !GC.isMobile() + Layout.fillWidth: true text: qsTr("Split site tunneling") @@ -105,9 +107,13 @@ PageType { } } - DividerType {} + DividerType { + visible: !GC.isMobile() + } LabelWithButtonType { + visible: !GC.isMobile() + Layout.fillWidth: true text: qsTr("Separate application tunneling") @@ -118,7 +124,9 @@ PageType { } } - DividerType {} + DividerType { + visible: !GC.isMobile() + } } } } diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index b79d5d22..f90c6719 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -20,6 +20,10 @@ import "../Components" PageType { id: root + property bool pageEnabled: { + return !ConnectionController.isConnected + } + Connections { target: SitesController @@ -78,6 +82,8 @@ PageType { RowLayout { HeaderType { + enabled: root.pageEnabled + Layout.fillWidth: true Layout.leftMargin: 16 @@ -89,6 +95,8 @@ PageType { property int lastActiveRouteMode: routeMode.onlyForwardSites + enabled: root.pageEnabled + Layout.fillWidth: true Layout.rightMargin: 16 @@ -115,7 +123,7 @@ PageType { drawerHeight: 0.4375 - enabled: switcher.checked + enabled: switcher.checked && root.pageEnabled headerText: qsTr("Mode") @@ -155,9 +163,9 @@ PageType { FlickableType { anchors.top: header.bottom anchors.topMargin: 16 - contentHeight: col.implicitHeight + connectButton.implicitHeight + connectButton.anchors.bottomMargin + connectButton.anchors.topMargin + contentHeight: col.implicitHeight + addSiteButton.implicitHeight + addSiteButton.anchors.bottomMargin + addSiteButton.anchors.topMargin - enabled: switcher.checked + enabled: switcher.checked && root.pageEnabled Column { id: col @@ -221,8 +229,17 @@ PageType { } } + Rectangle { + anchors.fill: addSiteButton + anchors.bottomMargin: -24 + color: "#0E0E11" + opacity: 0.8 + } + RowLayout { - id: connectButton + id: addSiteButton + + enabled: root.pageEnabled anchors.bottom: parent.bottom anchors.left: parent.left From ffc9e5823a7aa897e43d3b2d0690e7814b6c2a0f Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sat, 14 Oct 2023 18:21:49 +0500 Subject: [PATCH 217/278] text corrections --- client/containers/containers_defs.cpp | 88 ++++-- client/translations/amneziavpn_ru.ts | 231 ++++++++++------ client/translations/amneziavpn_zh_CN.ts | 255 +++++++++++++----- .../ConnectionTypeSelectionDrawer.qml | 9 +- .../Pages2/PageServiceTorWebsiteSettings.qml | 2 +- .../PageSetupWizardProtocolSettings.qml | 23 +- client/ui/qml/Pages2/PageShare.qml | 2 +- 7 files changed, 411 insertions(+), 199 deletions(-) diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index 4bd3b0ef..1c79874c 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -108,9 +108,10 @@ QMap ContainerProps::containerDescriptions() { DockerContainer::WireGuard, QObject::tr("WireGuard - New popular VPN protocol with high performance, high speed and low power " "consumption. Recommended for regions with low levels of censorship.") }, - { DockerContainer::Awg, - QObject::tr("AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, but very resistant to blockages. " - "Recommended for regions with high levels of censorship.") }, + { DockerContainer::Awg, + QObject::tr("AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, " + "but very resistant to blockages. " + "Recommended for regions with high levels of censorship.") }, { DockerContainer::Ipsec, QObject::tr("IKEv2 - Modern stable protocol, a bit faster than others, restores connection after " "signal loss. It has native support on the latest versions of Android and iOS.") }, @@ -125,19 +126,73 @@ QMap ContainerProps::containerDescriptions() QMap ContainerProps::containerDetailedDescriptions() { - return { { DockerContainer::OpenVpn, QObject::tr("OpenVPN container") }, - { DockerContainer::ShadowSocks, QObject::tr("Container with OpenVpn and ShadowSocks") }, - { DockerContainer::Cloak, - QObject::tr("Container with OpenVpn and ShadowSocks protocols " - "configured with traffic masking by Cloak plugin") }, - { DockerContainer::WireGuard, QObject::tr("WireGuard container") }, - { DockerContainer::WireGuard, QObject::tr("AmneziaWG container") }, - { DockerContainer::Ipsec, QObject::tr("IPsec container") }, + return { + { DockerContainer::OpenVpn, + QObject::tr( + "The time-tested most popular VPN protocol.\n\n" + "Uses a proprietary security protocol with SSL/TLS for encryption and key exchange and supports " + "various authentication methods, making it suitable for a variety of devices and operating " + "systems.\n\n" + "* Normal power consumption on mobile devices\n" + "* Flexible customisation to suit user needs to work with different operating systems and devices.\n" + "* Recognised by DPI analysis systems and therefore susceptible to blocking.\n" + "* Can operate over both TCP and UDP network protocols.") }, + { DockerContainer::ShadowSocks, + QObject::tr("Based on the SOCKS5 proxy protocol, which protects the connection using the AEAD cipher - " + "roughly along the same lines as SSH tunnelling. A Shadowsocks connection is difficult to " + "identify because it is virtually identical to a normal HTTPS connection.\n\n" + "However, some traffic analysis systems can still recognise a ShadowSocks connection, so in " + "countries with high levels of censorship we recommend using OpenVPN in conjunction with Cloak.\n" + "* Average power consumption on mobile devices (higher than OpenVPN).\n" + "* It is possible to configure the encryption protocol.\n" + "* Recognised by some DPI analysis systems\n" + "* Works only via TCP network protocol\n") }, + { DockerContainer::Cloak, + QObject::tr("This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for " + "blocking protection.\n\n" + "OpenVPN provides a secure VPN connection by encrypting all Internet traffic between the client " + "and the server.\n\n" + "Cloak protects OpenVPN from detection and blocking. \n\n" + "Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, " + "and also protects the VPN from detection by Active Probing. This makes it very resistant to " + "being detected\n\n" + "Immediately after receiving the first data packet, Cloak authenticates the incoming connection. " + "If authentication fails, the plugin masks the server as a fake website and your VPN becomes " + "invisible to analysis systems.\n\n" + "If there is a high level of Internet censorship in your region, we advise you to use only " + "OpenVPN over Cloak from the first connection\n" + "* High power consumption on mobile devices\n" + "* Flexible settings\n" + "* Not recognised by DPI analysis systems\n" + "* Works via TCP network protocol\n") }, + { DockerContainer::WireGuard, + QObject::tr("A relatively new popular VPN protocol with a simplified architecture.\n" + "Provides stable VPN connection, high performance on all devices. Uses hard-coded encryption " + "settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput.\n" - { DockerContainer::TorWebSite, QObject::tr("Website in Tor network") }, - { DockerContainer::Dns, QObject::tr("DNS Service") }, - //{DockerContainer::FileShare, QObject::tr("SMB file sharing service - is Window file sharing protocol")}, - { DockerContainer::Sftp, QObject::tr("Sftp file sharing service - is secure FTP service") } }; + "* Low power consumption on mobile devices.\n" + "* Minimum number of settings.\n" + "* Easily recognised by DPI analysis systems, susceptible to blocking.\n" + "* Works via UDP network protocol.\n") }, + { DockerContainer::Awg, QObject::tr("AmneziaWG container") }, + { DockerContainer::Ipsec, + QObject::tr("A modern stable protocol.\n\n" + + "IKEv2 with IPSec encryption layer. Transmits data over fixed UDP ports 500 and 4500 protecting " + "them with strong 3DES and AES crypto algorithms. Allows very fast switching between networks " + "and devices. Due to its security, stability and speed, IKEv2 is currently one of the best VPN " + "solutions for mobile devices. Vulnerable to detection and blocking.\n" + + "* Low power consumption, on mobile devices\n" + "* Minimal configuration.\n" + "* Recognised by DPI analysis systems.\n" + "* Works only over UDP network protocol\n") }, + + { DockerContainer::TorWebSite, QObject::tr("Website in Tor network") }, + { DockerContainer::Dns, QObject::tr("DNS Service") }, + //{DockerContainer::FileShare, QObject::tr("SMB file sharing service - is Window file sharing protocol")}, + { DockerContainer::Sftp, QObject::tr("Sftp file sharing service - is secure FTP service") } + }; } amnezia::ServiceType ContainerProps::containerService(DockerContainer c) @@ -254,7 +309,8 @@ QString ContainerProps::easySetupDescription(DockerContainer container) switch (container) { case DockerContainer::WireGuard: return tr("I just want to increase the level of my privacy."); case DockerContainer::Awg: return tr("I want to bypass censorship. This option recommended in most cases."); - case DockerContainer::Cloak: return tr("Most VPN protocols are blocked. Recommended if other options are not working."); + case DockerContainer::Cloak: + return tr("Most VPN protocols are blocked. Recommended if other options are not working."); default: return ""; } } diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index 8a2ffffc..94ce0b3a 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -68,17 +68,22 @@ ConnectionTypeSelectionDrawer - - Connection data + + Add server - + + Select data type + + + + Server IP, login and password - + QR code, key or configuration file @@ -130,7 +135,7 @@ ImportController - + Scanned %1 of %2. @@ -808,7 +813,7 @@ Already installed containers were found on the server. All installed containers - When configuring WordPress set the domain as this onion address. + When configuring WordPress set the this address as domain. @@ -865,7 +870,7 @@ Already installed containers were found on the server. All installed containers - + Close application @@ -1143,22 +1148,22 @@ Already installed containers were found on the server. All installed containers - + Site-based split tunneling - + Allows you to select which sites you want to access through the VPN - + App-based split tunneling - + Allows you to use the VPN only for certain applications @@ -1438,90 +1443,90 @@ Already installed containers were found on the server. All installed containers PageSettingsSplitTunneling - + Addresses from the list should be accessed via VPN - + Addresses from the list should not be accessed via VPN - + Split tunneling - + Mode - + Remove - + Continue Продолжить - + Cancel - + Site or IP - + Import/Export Sites - + Import - + Save site list - + Save sites - - - + + + Sites files (*.json) - + Import a list of sites - + Replace site list - - + + Open sites file - + Add imported sites to existing ones @@ -1699,22 +1704,22 @@ It's okay as long as it's from someone you trust. - + Close - + Network protocol - + Port - + Install @@ -1851,6 +1856,11 @@ It's okay as long as it's from someone you trust. VPN access without the ability to manage the server + + + Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the server, as well as change settings. + + @@ -1897,11 +1907,6 @@ It's okay as long as it's from someone you trust. Full access - - - Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the servers, as well as change settings. - - @@ -2376,7 +2381,74 @@ It's okay as long as it's from someone you trust. - + + The time-tested most popular VPN protocol. + +Uses a proprietary security protocol with SSL/TLS for encryption and key exchange and supports various authentication methods, making it suitable for a variety of devices and operating systems. + +* Normal power consumption on mobile devices +* Flexible customisation to suit user needs to work with different operating systems and devices. +* Recognised by DPI analysis systems and therefore susceptible to blocking. +* Can operate over both TCP and UDP network protocols. + + + + + Based on the SOCKS5 proxy protocol, which protects the connection using the AEAD cipher - roughly along the same lines as SSH tunnelling. A Shadowsocks connection is difficult to identify because it is virtually identical to a normal HTTPS connection. + +However, some traffic analysis systems can still recognise a ShadowSocks connection, so in countries with high levels of censorship we recommend using OpenVPN in conjunction with Cloak. +* Average power consumption on mobile devices (higher than OpenVPN). +* It is possible to configure the encryption protocol. +* Recognised by some DPI analysis systems +* Works only via TCP network protocol + + + + + + This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for blocking protection. + +OpenVPN provides a secure VPN connection by encrypting all Internet traffic between the client and the server. + +Cloak protects OpenVPN from detection and blocking. + +Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, and also protects the VPN from detection by Active Probing. This makes it very resistant to being detected + +Immediately after receiving the first data packet, Cloak authenticates the incoming connection. If authentication fails, the plugin masks the server as a fake website and your VPN becomes invisible to analysis systems. + +If there is a high level of Internet censorship in your region, we advise you to use only OpenVPN over Cloak from the first connection +* High power consumption on mobile devices +* Flexible settings +* Not recognised by DPI analysis systems +* Works via TCP network protocol + + + + + + A relatively new popular VPN protocol with a simplified architecture. +Provides stable VPN connection, high performance on all devices. Uses hard-coded encryption settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput. +* Low power consumption on mobile devices. +* Minimum number of settings. +* Easily recognised by DPI analysis systems, susceptible to blocking. +* Works via UDP network protocol. + + + + + + A modern stable protocol. + +IKEv2 with IPSec encryption layer. Transmits data over fixed UDP ports 500 and 4500 protecting them with strong 3DES and AES crypto algorithms. Allows very fast switching between networks and devices. Due to its security, stability and speed, IKEv2 is currently one of the best VPN solutions for mobile devices. Vulnerable to detection and blocking. +* Low power consumption, on mobile devices +* Minimal configuration. +* Recognised by DPI analysis systems. +* Works only over UDP network protocol + + + + + DNS Service @@ -2387,7 +2459,7 @@ It's okay as long as it's from someone you trust. - + Website in Tor network @@ -2413,62 +2485,41 @@ It's okay as long as it's from someone you trust. - WireGuard - New popular VPN protocol with high performance, high speed and low power consumption. Recommended for regions with low levels of censorship. - + + AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, but very resistant to blockages. Recommended for regions with high levels of censorship. + + + + IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS. - + Deploy a WordPress site on the Tor network in two clicks. - + Replace the current DNS server with your own. This will increase your privacy level. - + Creates a file vault on your server to securely store and transfer files. - - OpenVPN container - - - - - Container with OpenVpn and ShadowSocks - - - - - Container with OpenVpn and ShadowSocks protocols configured with traffic masking by Cloak plugin - - - - - WireGuard container - - - - + AmneziaWG container - - IPsec container - - - - + Sftp file sharing service - is secure FTP service @@ -2537,6 +2588,16 @@ It's okay as long as it's from someone you trust. error 0x%1: %2 + + + WireGuard Configuration Highlighter + + + + + &Randomize colors + + SelectLanguageDrawer @@ -2755,33 +2816,33 @@ It's okay as long as it's from someone you trust. amnezia::ContainerProps - + Low - - High + + Medium or High - - Medium + + Extreme - - Many foreign websites and VPN providers are blocked + + I just want to increase the level of my privacy. - - Some foreign sites are blocked, but VPN providers are not blocked + + I want to bypass censorship. This option recommended in most cases. - - I just want to increase the level of privacy + + Most VPN protocols are blocked. Recommended if other options are not working. diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index e60f0d8b..359655f8 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -68,17 +68,26 @@ ConnectionTypeSelectionDrawer - Connection data - 连接方式 + 连接方式 - + + Add server + + + + + Select data type + + + + Server IP, login and password 服务器IP,用户名和密码 - + QR code, key or configuration file 二维码,授权码或者配置文件 @@ -130,7 +139,7 @@ ImportController - + Scanned %1 of %2. 扫描 %1 of %2. @@ -847,8 +856,12 @@ Already installed containers were found on the server. All installed containers + When configuring WordPress set the this address as domain. + + + When configuring WordPress set the domain as this onion address. - 配置 WordPress 时,将域设置为此洋葱地址。 + 配置 WordPress 时,将域设置为此洋葱地址。 @@ -904,7 +917,7 @@ Already installed containers were found on the server. All installed containers 关于 - + Close application 关闭应用 @@ -1205,17 +1218,17 @@ And if you don't like the app, all the more support it - the donation will 如果未使用或未安装AmneziaDNS - + Site-based split tunneling 基于网站的隧道分离 - + Allows you to select which sites you want to access through the VPN 配置想要通过VPN访问网站 - + App-based split tunneling 基于应用的隧道分离 @@ -1232,7 +1245,7 @@ And if you don't like the app, all the more support it - the donation will 应用级VPN分流 - + Allows you to use the VPN only for certain applications 仅指定应用使用VPN @@ -1532,90 +1545,90 @@ And if you don't like the app, all the more support it - the donation will 网站级VPN分流 - + Addresses from the list should be accessed via VPN 仅使用VPN访问 - + Addresses from the list should not be accessed via VPN 不使用VPN访问 - + Split tunneling 隧道分离 - + Mode 规则 - + Remove 移除 - + Continue 继续 - + Cancel 取消 - + Site or IP 网站或IP地址 - + Import/Export Sites 导入/导出网站 - + Import 导入 - + Save site list 保存网址 - + Save sites 保存网址 - - - + + + Sites files (*.json) - + Import a list of sites 导入网址列表 - + Replace site list 替换网址列表 - - + + Open sites file 打开网址文件 - + Add imported sites to existing ones 将导入的网址添加到现有网址中 @@ -1794,22 +1807,22 @@ It's okay as long as it's from someone you trust. 更多细节 - + Close 关闭 - + Network protocol 网络协议 - + Port 端口 - + Install 安装 @@ -1968,8 +1981,12 @@ It's okay as long as it's from someone you trust. + Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the server, as well as change settings. + + + Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the servers, as well as change settings. - 除访问VPN外,用户还能添加和删除协议、服务以及更改配置信息 + 除访问VPN外,用户还能添加和删除协议、服务以及更改配置信息 Full access to server @@ -2489,7 +2506,7 @@ It's okay as long as it's from someone you trust. - + Website in Tor network 在 Tor 网络中架设网站 @@ -2520,67 +2537,133 @@ It's okay as long as it's from someone you trust. - WireGuard - New popular VPN protocol with high performance, high speed and low power consumption. Recommended for regions with low levels of censorship. WireGuard - 新型流行的VPN协议,具有高性能、高速度和低功耗。建议用于审查力度较低的地区 - + + AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, but very resistant to blockages. Recommended for regions with high levels of censorship. + + + + IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS. IKEv2 - 现代稳定协议,相比其他协议较快一些,在信号丢失后恢复连接。Android 和 iOS最新版原生支持。 - + Deploy a WordPress site on the Tor network in two clicks. 只需点击两次即可架设 WordPress 网站到 Tor 网络 - + Replace the current DNS server with your own. This will increase your privacy level. 将当前的 DNS 服务器替换为您自己的。这将提高您的隐私保护级别。 - + Creates a file vault on your server to securely store and transfer files. 在您的服务器上创建文件仓库,以便安全地存储和传输文件 - - - OpenVPN container - OpenVPN容器 - - - - Container with OpenVpn and ShadowSocks - 含 OpenVpn 和 ShadowSocks 的容器 - + The time-tested most popular VPN protocol. + +Uses a proprietary security protocol with SSL/TLS for encryption and key exchange and supports various authentication methods, making it suitable for a variety of devices and operating systems. + +* Normal power consumption on mobile devices +* Flexible customisation to suit user needs to work with different operating systems and devices. +* Recognised by DPI analysis systems and therefore susceptible to blocking. +* Can operate over both TCP and UDP network protocols. + + + + + Based on the SOCKS5 proxy protocol, which protects the connection using the AEAD cipher - roughly along the same lines as SSH tunnelling. A Shadowsocks connection is difficult to identify because it is virtually identical to a normal HTTPS connection. + +However, some traffic analysis systems can still recognise a ShadowSocks connection, so in countries with high levels of censorship we recommend using OpenVPN in conjunction with Cloak. +* Average power consumption on mobile devices (higher than OpenVPN). +* It is possible to configure the encryption protocol. +* Recognised by some DPI analysis systems +* Works only via TCP network protocol + + + + + + This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for blocking protection. + +OpenVPN provides a secure VPN connection by encrypting all Internet traffic between the client and the server. + +Cloak protects OpenVPN from detection and blocking. + +Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, and also protects the VPN from detection by Active Probing. This makes it very resistant to being detected + +Immediately after receiving the first data packet, Cloak authenticates the incoming connection. If authentication fails, the plugin masks the server as a fake website and your VPN becomes invisible to analysis systems. + +If there is a high level of Internet censorship in your region, we advise you to use only OpenVPN over Cloak from the first connection +* High power consumption on mobile devices +* Flexible settings +* Not recognised by DPI analysis systems +* Works via TCP network protocol + + + + + + A relatively new popular VPN protocol with a simplified architecture. +Provides stable VPN connection, high performance on all devices. Uses hard-coded encryption settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput. +* Low power consumption on mobile devices. +* Minimum number of settings. +* Easily recognised by DPI analysis systems, susceptible to blocking. +* Works via UDP network protocol. + + + + + + A modern stable protocol. + +IKEv2 with IPSec encryption layer. Transmits data over fixed UDP ports 500 and 4500 protecting them with strong 3DES and AES crypto algorithms. Allows very fast switching between networks and devices. Due to its security, stability and speed, IKEv2 is currently one of the best VPN solutions for mobile devices. Vulnerable to detection and blocking. +* Low power consumption, on mobile devices +* Minimal configuration. +* Recognised by DPI analysis systems. +* Works only over UDP network protocol + + + + + OpenVPN container + OpenVPN容器 + + + Container with OpenVpn and ShadowSocks + 含 OpenVpn 和 ShadowSocks 的容器 + + Container with OpenVpn and ShadowSocks protocols configured with traffic masking by Cloak plugin - 含 OpenVpn 和 ShadowSocks 协议的容器,通过 Cloak 插件配置混淆流量 + 含 OpenVpn 和 ShadowSocks 协议的容器,通过 Cloak 插件配置混淆流量 - WireGuard container - WireGuard 容器 + WireGuard 容器 - + AmneziaWG container - IPsec container - IPsec 容器 + IPsec 容器 - + DNS Service DNS 服务 - + Sftp file sharing service - is secure FTP service Sftp 文件共享服务 - 安全的 FTP 服务 @@ -2644,6 +2727,16 @@ It's okay as long as it's from someone you trust. error 0x%1: %2 错误 0x%1: %2 + + + WireGuard Configuration Highlighter + + + + + &Randomize colors + + SelectLanguageDrawer @@ -2866,34 +2959,54 @@ It's okay as long as it's from someone you trust. amnezia::ContainerProps - + Low - + + Medium or High + + + + + Extreme + + + + + I just want to increase the level of my privacy. + + + + + I want to bypass censorship. This option recommended in most cases. + + + + + Most VPN protocols are blocked. Recommended if other options are not working. + + + High - + - Medium - + - I just want to increase the level of privacy - 我只是想提高隐私保护级别 + 我只是想提高隐私保护级别 - Many foreign websites and VPN providers are blocked - 大多国外网站和VPN提供商被屏蔽 + 大多国外网站和VPN提供商被屏蔽 - Some foreign sites are blocked, but VPN providers are not blocked - 一些国外网站被屏蔽,但VPN提供商未被屏蔽 + 一些国外网站被屏蔽,但VPN提供商未被屏蔽 diff --git a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml index ecde1554..6128c652 100644 --- a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml +++ b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml @@ -20,16 +20,15 @@ DrawerType { anchors.right: parent.right spacing: 0 - Header2TextType { + Header2Type { Layout.fillWidth: true Layout.topMargin: 24 Layout.rightMargin: 16 Layout.leftMargin: 16 - Layout.bottomMargin: 32 - Layout.alignment: Qt.AlignHCenter + Layout.bottomMargin: 16 - text: qsTr("Connection data") - wrapMode: Text.WordWrap + headerText: qsTr("Add server") + descriptionText: qsTr("Select data type") } LabelWithButtonType { diff --git a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml index 04d7076c..0d5baa3d 100644 --- a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml +++ b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml @@ -121,7 +121,7 @@ PageType { Layout.leftMargin: 16 Layout.rightMargin: 16 - text: qsTr("When configuring WordPress set the domain as this onion address.") + text: qsTr("When configuring WordPress set the this address as domain.") } BasicButtonType { diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 7535464a..2b97f044 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -144,33 +144,16 @@ PageType { headerText: name } - TextField { - implicitWidth: parent.width + ParagraphTextType { Layout.fillWidth: true Layout.topMargin: 16 Layout.bottomMargin: 16 - padding: 0 - leftPadding: 0 - height: 24 - - color: "#D7D8DB" - - font.pixelSize: 16 - font.weight: Font.Medium - font.family: "PT Root UI VF" - text: detailedDescription - - wrapMode: Text.WordWrap - - readOnly: true - background: Rectangle { - anchors.fill: parent - color: "transparent" - } + textFormat: Text.MarkdownText } + Rectangle { Layout.fillHeight: true color: "transparent" diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index a92d3e51..aa04a1fe 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -172,7 +172,7 @@ PageType { Layout.bottomMargin: 24 text: accessTypeSelector.currentIndex === 0 ? qsTr("VPN access without the ability to manage the server") : - qsTr("Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the servers, as well as change settings.") + qsTr("Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the server, as well as change settings.") color: "#878B91" } From 3ac09181c6065afd40f5e281a20249b866058439 Mon Sep 17 00:00:00 2001 From: pokamest Date: Sat, 14 Oct 2023 15:55:07 +0100 Subject: [PATCH 218/278] Text lables fixes --- .../qml/Components/ConnectionTypeSelectionDrawer.qml | 6 +++--- client/ui/qml/Pages2/PageSetupWizardCredentials.qml | 10 +++++++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml index ecde1554..81ccd245 100644 --- a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml +++ b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml @@ -28,7 +28,7 @@ DrawerType { Layout.bottomMargin: 32 Layout.alignment: Qt.AlignHCenter - text: qsTr("Connection data") + text: qsTr("Add new connection") wrapMode: Text.WordWrap } @@ -37,7 +37,7 @@ DrawerType { Layout.fillWidth: true Layout.topMargin: 16 - text: qsTr("Server IP, login and password") + text: qsTr("Configure your server") rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { @@ -51,7 +51,7 @@ DrawerType { LabelWithButtonType { Layout.fillWidth: true - text: qsTr("QR code, key or configuration file") + text: qsTr("Open QR code, key or config file") rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index bc24c196..5c32b0c5 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -41,7 +41,7 @@ PageType { HeaderType { Layout.fillWidth: true - headerText: qsTr("Server connection") + headerText: qsTr("Configure your server") } TextFieldWithHeaderType { @@ -107,6 +107,14 @@ PageType { PageController.goToPage(PageEnum.PageSetupWizardEasy) } } + + LabelTextType { + Layout.fillWidth: true + Layout.topMargin: 12 + + text: qsTr("All data you enter will remain strictly confidential +and will not be shared or disclosed to the Amnezia or any third parties") + } } } From b6d2030041d2924bd1b661bda1af846be65c6022 Mon Sep 17 00:00:00 2001 From: pokamest Date: Sat, 14 Oct 2023 15:55:52 +0100 Subject: [PATCH 219/278] Ru translation --- client/translations/amneziavpn_ru.ts | 1076 +++++++++++++------------- 1 file changed, 559 insertions(+), 517 deletions(-) diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index ac099552..d2ad31b7 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -6,7 +6,7 @@ Split tunneling for WireGuard is not implemented, the option was disabled - + Раздельное туннелирование для "Wireguard" не реализовано,опция отключена @@ -14,13 +14,13 @@ AmneziaVPN - + AmneziaVPN VPN Connected Refers to the app - which is currently running the background and waiting - + VPN Подключен @@ -29,27 +29,28 @@ VPN Protocols is not installed. Please install VPN container at first - + VPN протоколы не установлены. + Пожалуйста, установите протокол Connection... - + Подключение... Connected - + Подключено Settings updated successfully, Reconnnection... - + Настройки успешно обновлены. Подключение... Reconnection... - + Переподключение... @@ -57,29 +58,41 @@ Connect - + Подключиться Disconnection... - + Отключение... ConnectionTypeSelectionDrawer - Connection data + Данные для подлкючения + + + Server IP, login and password + IP сервера, логин и пароль + + + QR code, key or configuration file + QR-код, ключ, файл настроек или бекап + + + + Add new connection - Server IP, login and password + Configure your server - QR code, key or configuration file + Open QR code, key or config file @@ -88,22 +101,22 @@ C&ut - + &Вырезать &Copy - + &Копировать &Paste - + &Вставить &SelectAll - + &ВыбратьВсе @@ -111,7 +124,7 @@ Access error! - + Ошибка доступа! @@ -119,12 +132,12 @@ The selected protocol is not supported on the current platform - + Выбранный протокол не поддерживается на данном устройстве Reconnect via VPN Procotol: - + Переподключение через VPN протокол: @@ -132,7 +145,7 @@ Scanned %1 of %2. - + Отсканировано %1 из%2. @@ -141,55 +154,57 @@ %1 installed successfully. - + %1 успешно установлен. %1 is already installed on the server. - + %1 уже установлен на сервер. Added containers that were already installed on the server - + +В приложение добавлены обнаруженные на сервере протоклы и сервисы Already installed containers were found on the server. All installed containers have been added to the application - + +На сервере обнаружены установленные протоколы и сервисы, все они добавлены в приложение Settings updated successfully - + Настройки успешно обновлены Server '%1' was removed - + Сервер '%1' был удален All containers from server '%1' have been removed - + Все протоклы и сервисы были удалены с сервера '%1' %1 has been removed from the server '%2' - + %1 был удален с сервера '%2' Please login as the user - + Пожалуйста, войдите в систему от имени пользователя Server added successfully - + Сервер успешно добавлен @@ -197,17 +212,17 @@ Already installed containers were found on the server. All installed containers Read key failed: %1 - + Не удалось считать ключ: %1 Write key failed: %1 - + Не удалось записать ключ: %1 Delete key failed: %1 - + Не удалось удалить ключ: %1 @@ -216,27 +231,27 @@ Already installed containers were found on the server. All installed containers AmneziaVPN - + AmneziaVPN VPN Connected - + VPN Подключен VPN Disconnected - + VPN Выключен AmneziaVPN notification - + Уведомление AmneziaVPN Unsecured network detected: - + Обнаружена незащищенная сеть: @@ -244,12 +259,12 @@ Already installed containers were found on the server. All installed containers Removing services from %1 - + Удаление сервисов c %1 Usually it takes no more than 5 minutes - + Обычно это занимает не более 5 минут @@ -257,12 +272,12 @@ Already installed containers were found on the server. All installed containers VPN protocol - + VPN протокол Servers - + Серверы @@ -270,87 +285,87 @@ Already installed containers were found on the server. All installed containers AmneziaWG settings - + AmneziaWG настройки Port - + Порт Junk packet count - + Junk packet count Junk packet minimum size - + Junk packet minimum size Junk packet maximum size - + Junk packet maximum size Init packet junk size - + Init packet junk size Response packet junk size - + Response packet junk size Init packet magic header - + Init packet magic header Response packet magic header - + Response packet magic header Transport packet magic header - + Transport packet magic header Underload packet magic header - + Underload packet magic header Remove AmneziaWG - + Remove AmneziaWG Remove AmneziaWG from server? - + Удалить AmneziaWG с сервера? All users who you shared a connection with will no longer be able to connect to it. - + Все пользователи, которым вы поделились VPN с этим протоколом, больше не смогут к нему подключаться. Continue - Продолжить + Продолжить Cancel - + Отменить Save and Restart Amnezia - + Сохранить и пререзагрузить Amnezia @@ -358,28 +373,28 @@ Already installed containers were found on the server. All installed containers Cloak settings - + Настройки Cloak Disguised as traffic from - + Замаскировать трафик под Port - + Port Cipher - + Cipher Save and Restart Amnezia - + Сохранить и перезагрузить Amnezia @@ -387,195 +402,195 @@ Already installed containers were found on the server. All installed containers OpenVPN settings - + Настройки OpenVPN VPN Addresses Subnet - + VPN Адреса Подсеть Network protocol - + Сетевой протокол Port - + Порт Auto-negotiate encryption - + Шифрование с автоматическим согласованием Hash - + Хэш SHA512 - + SHA512 SHA384 - + SHA384 SHA256 - + SHA256 SHA3-512 - + SHA3-512 SHA3-384 - + SHA3-384 SHA3-256 - + SHA3-256 whirlpool - + whirlpool BLAKE2b512 - + BLAKE2b512 BLAKE2s256 - + BLAKE2s256 SHA1 - + SHA1 Cipher - + Шифрованаие AES-256-GCM - + AES-256-GCM AES-192-GCM - + AES-192-GCM AES-128-GCM - + AES-128-GCM AES-256-CBC - + AES-256-CBC AES-192-CBC - + AES-192-CBC AES-128-CBC - + AES-128-CBC ChaCha20-Poly1305 - + ChaCha20-Poly1305 ARIA-256-CBC - + ARIA-256-CBC CAMELLIA-256-CBC - + CAMELLIA-256-CBC none - + none TLS auth - + TLS авторизация Block DNS requests outside of VPN - + Блокировать DNS запросы за пределами VPN Additional client configuration commands - + Дополнительные команды конфигурации клиента Commands: - + Commands: Additional server configuration commands - + Дополнительные команды конфигурации сервера Remove OpenVPN - + Удалить OpenVPN Remove OpenVpn from server? - + Удалить OpenVpn с сервера? All users who you shared a connection with will no longer be able to connect to it. - + Все пользователи, которым вы поделились VPN, больше не смогут к нему подключаться. Continue - Продолжить + Продолжить Cancel - + Отменить Save and Restart Amnezia - + Сохранить и перезагрузить @@ -583,42 +598,42 @@ Already installed containers were found on the server. All installed containers settings - + настройки Show connection options - + Показать параметры подключения Connection options %1 - + Параметры подключения %1 Remove - + Удалить Remove %1 from server? - + Удалить %1 с сервера? All users who you shared a connection with will no longer be able to connect to it. - + Все пользователи, которым вы поделились VPN с этим протоколом больше не смогут к нему подключаться. Continue - Продолжить + Продолжить Cancel - + Отменить @@ -626,23 +641,23 @@ Already installed containers were found on the server. All installed containers ShadowSocks settings - + Настройки ShadowSocks Port - + Порт Cipher - + Шифрование Save and Restart Amnezia - + Сохранить и перезагрузить Amnezia @@ -658,32 +673,33 @@ Already installed containers were found on the server. All installed containers A DNS service is installed on your server, and it is only accessible via VPN. - + На вашем сервере устанавливается DNS-сервис, доступ к нему возможен только через VPN. + The DNS address is the same as the address of your server. You can configure DNS in the settings, under the connections tab. - + Адрес DNS совпадает с адресом вашего сервера. Настроить DNS можно в настройках, во вкладке "Соединения". Remove - + Удалить Remove %1 from server? - + Удалить %1 с сервера? Continue - Продолжить + Продолжить Cancel - + Отменить @@ -691,17 +707,17 @@ Already installed containers were found on the server. All installed containers Settings updated successfully - + Настройки успешно обновлены SFTP settings - + Настройки SFTP Host - + Хост @@ -709,69 +725,69 @@ Already installed containers were found on the server. All installed containers Copied - + Скопировано Port - + Порт Login - + Логин Password - + Пароль Mount folder on device - + Смонтировать папку на вашем устройстве In order to mount remote SFTP folder as local drive, perform following steps: <br> - + Чтобы смонтировать SFTP-папку как локальный диск на вашем устройстве, выполните следующие действия <br>1. Install the latest version of - + <br>1. Установите последнюю версию <br>2. Install the latest version of - + <br>2. Установите последнюю версию Detailed instructions - + Подробные инструкции Remove SFTP and all data stored there - + Удалите SFTP-хранилище со всеми данными Remove SFTP and all data stored there? - + Удалить SFTP-хранилище и все хранящиеся на нем данные? Continue - Продолжить + Продолжить Cancel - + Отменить @@ -779,57 +795,57 @@ Already installed containers were found on the server. All installed containers Settings updated successfully - + Настройки успешно обновлены Tor website settings - + Настройки сайта в сети Тоr Website address - + Адрес сайта Copied - + Скопировано Use <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor Browser</a> to open this url. - + Используйте <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor Browser</a> для открытия этой ссылки. After installation it takes several minutes while your onion site will become available in the Tor Network. - + Через несколько минут после установки ваш Onion сайт станет доступен в сети Tor. When configuring WordPress set the domain as this onion address. - + При настройке WordPress, укажите этот адрес в качестве домена. Remove website - + Удалить сайт The site with all data will be removed from the tor network. - + Сайт со всеми данными будет удален из сети tor. Continue - Продолжить + Продолжить Cancel - + Отменить @@ -837,37 +853,37 @@ Already installed containers were found on the server. All installed containers Settings - + Настройки Servers - + Серверы Connection - + Соединение Application - + Приложение Backup - + Резервное копирование About AmneziaVPN - + Об AmneziaVPN Close application - + Закрыть приложение @@ -875,87 +891,87 @@ Already installed containers were found on the server. All installed containers Support the project with a donation - + Поддержите проект донатами This is a free and open source application. If you like it, support the developers with a donation. - + Это бесплатное приложение с открытым исходным кодом. Если, оно вам нравится - поддержите разработчиков пожертвованием. And if you don’t like the application, all the more reason to support it - the donation will be used for the improving the application. - + А, если оно вам не нравится, тем более поддержите-пожертвование пойдет на улучшение приложения. Card on Patreon - + Картой на Patreon https://www.patreon.com/amneziavpn - + https://www.patreon.com/amneziavpn Show other methods on Github - + Показать другие способы на Github Contacts - + Контакты Telegram group - + Группа в Telegram To discuss features - + Для обсуждений https://t.me/amnezia_vpn_en - + https://t.me/amnezia_vpn Mail - + Почта For reviews and bug reports - + Для отзывов и сообщений об ошибках Github - + Github https://github.com/amnezia-vpn/amnezia-client - + https://github.com/amnezia-vpn/amnezia-client Website - + Веб-сайт https://amnezia.org - + https://amnezia.org Check for updates - + Проверить обновления @@ -963,77 +979,77 @@ Already installed containers were found on the server. All installed containers Application - + Приложение Allow application screenshots - + Разрешить скриншоты Auto start - + Авто-запуск Launch the application every time %1 starts - + Запускать приложение при каждом включении %1 Start minimized - + Запуск в свернутом виде Launch application minimized - + Запуск приложения в свернутом виде Language - + Язык Logging - + Логирование Enabled - + Включено Disabled - + Отключено Reset settings and remove all data from the application - + Сбросить настройки и удалить все данные из приложения Reset settings and remove all data from the application? - + Сбросить настройки и удалить все данные из приложения? All settings will be reset to default. All installed AmneziaVPN services will still remain on the server. - + Все данные из приложения будут удалены Все установленные сервисы AmneziaVPN останутся на сервере. Continue - Продолжить + Продолжить Cancel - + Отменить @@ -1041,68 +1057,68 @@ Already installed containers were found on the server. All installed containers Backup - + Резервное копирование Settings restored from backup file - + Восстановление настроек из бэкап файла Configuration backup - + Бэкап конфигурация You can save your settings to a backup file to restore them the next time you install the application. - + Поможет мгновенно восстановить настройки соединений при следующей установке. Make a backup - + Сделать бэкап Save backup file - + Сохранить бэкап файл Backup files (*.backup) - + Файлы резервного копирования (*.backup) Restore from backup - + Восстановить из бэкапа Open backup file - + Открыть бэкап файл Import settings from a backup file? - + Импортировать настройки из бэкап файла? All current settings will be reset - + Все текущие настройки будут сброшены Continue - Продолжить + Продолжить Cancel - + Отменить @@ -1110,57 +1126,57 @@ Already installed containers were found on the server. All installed containers Connection - + Подключение Auto connect - + Автоподключение Connect to VPN on app start - + Подключение к VPN при запуске приложения Use AmneziaDNS - + Использовать Amnezia DNS If AmneziaDNS is installed on the server - + Если он уставновлен на сервере DNS servers - + DNS сервер If AmneziaDNS is not used or installed - + Эти серверы будут использоваться, если не включен AmneziaDNS Site-based split tunneling - + Раздельное туннелирование сайтов Allows you to select which sites you want to access through the VPN - + Позволяет подключаться к одним сайтам через VPN, а к другим в обход него App-based split tunneling - + Раздельное VPN-туннелирование приложений Allows you to use the VPN only for certain applications - + Позволяет использовать VPN только для определённых приложений @@ -1168,57 +1184,57 @@ Already installed containers were found on the server. All installed containers DNS servers - + DNS сервер If AmneziaDNS is not used or installed - + Эти адреса будут использоваться, если не включен или не установлен AmneziaDNS Primary DNS - + Первичный DNS Secondary DNS - + Вторичный DNS Restore default - + Восстановить по умолчанию Restore default DNS settings? - + Восстановить настройки DNS по умолчанию? Continue - Продолжить + Продолжить Cancel - + Отменить Settings have been reset - + Настройки сброшены Save - + Сохранить Settings saved - + Сохранить настройки @@ -1226,57 +1242,57 @@ Already installed containers were found on the server. All installed containers Logging - + Логирование Save logs - + Сохранить логи Open folder with logs - + Открыть папку с логами Save - + Сохранить Logs files (*.log) - + Logs files (*.log) Save logs to file - + Сохранить логи в файл Clear logs? - + Очистить логи? Continue - Продолжить + Продолжить Cancel - + Отменить Logs have been cleaned up - + Логи удалены Clear logs - + Удалить логи @@ -1284,27 +1300,27 @@ Already installed containers were found on the server. All installed containers All installed containers have been added to the application - + Все установленные протоколы и сервисы были добавлены в приложение Clear Amnezia cache - + Очистить кэш Amnezia на сервере May be needed when changing other settings - + Может понадобиться при изменении других настроек Clear cached profiles? - Очистить закешированные профили + Удалить кэш Amnezia с сервера? No new installed containers found - + Новые установленные протоколы и сервисы не обнаружены @@ -1323,47 +1339,47 @@ Already installed containers were found on the server. All installed containers Cancel - + Отменить Check the server for previously installed Amnezia services - + Проверка сервера на наличие ранее установленных сервисов Amnezia Add them to the application if they were not displayed - + Добавить их в приложение, если они не были отображены Remove server from application - + Удалить сервер из приложения Remove server? - + Удалить сервер? All installed AmneziaVPN services will still remain on the server. - + Все установленные сервисы и протоколы Amnezia всё ещё останутся на сервере. Clear server from Amnezia software - + Очистка сервера от протоколов и сервисов Amnezia Clear server from Amnezia software? - + Удалить все сервисы и протоколы Amnezia с сервера? All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted. - + На сервере будут удалены все, что связанно с Amnezia: протоколы сервисы конфигурационные файлы, ключи и сертификаты. @@ -1371,27 +1387,27 @@ Already installed containers were found on the server. All installed containers Server name - + Имя сервера Save - + Сохранить Protocols - + Протоколы Services - + Сервисы Data - + Данные @@ -1399,32 +1415,32 @@ Already installed containers were found on the server. All installed containers settings - + настройки Remove - + Удалить Remove %1 from server? - + Удалить %1 с сервера? All users who you shared a connection with will no longer be able to connect to it. - + Все пользователи, которым вы поделились VPN, больше не смогут к нему подключаться. Continue - Продолжить + Продолжить Cancel - + Отменить @@ -1432,7 +1448,7 @@ Already installed containers were found on the server. All installed containers Servers - + Серверы @@ -1440,90 +1456,90 @@ Already installed containers were found on the server. All installed containers Addresses from the list should be accessed via VPN - + Только адреса из списка должны открываться через VPN Addresses from the list should not be accessed via VPN - + Адреса из списка не должны открываться через VPN Split tunneling - + Раздельно VPN-туннелирование Mode - + Режим Remove - + Удалить Continue - Продолжить + Продолжить Cancel - + Отменить Site or IP - + Сайт или IP Import/Export Sites - + Импорт/экспорт Сайтов Import - + Импорт Save site list - + Сохранить список сайтов Save sites - + Сохранить Sites files (*.json) - + Sites files (*.json) Import a list of sites - + Импортировать список с сайтами Replace site list - + Заменить список сайтов Open sites file - + Открыть список с сайтами Add imported sites to existing ones - + Добавление импортированных сайтов к существующим @@ -1531,44 +1547,46 @@ Already installed containers were found on the server. All installed containers Server connection - + Подключение к серверу Do not use connection code from public sources. It may have been created to intercept your data. It's okay as long as it's from someone you trust. - + Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватывать ваши данные.. + +Всё в порядке, если кодом поделился пользователь, которому вы доверяете. What do you have? - + Выберете что у вас есть? File with connection settings - + Файл с настройками подключения File with connection settings or backup - + Файл с настройками подключения или бэкап Open config file - + Открыть файл с конфигурацией QR-code - + QR-код Key as text - + Ключ в виде текста @@ -1576,52 +1594,52 @@ It's okay as long as it's from someone you trust. Server connection - + Подключение к серверу Server IP address [:port] - + Server IP address [:port] 255.255.255.255:88 - + 255.255.255.255:88 Password / SSH private key - + Password / SSH private key Continue - Продолжить + Продолжить Enter the address in the format 255.255.255.255:88 - + Введите адрес в формате 255.255.255.255:88 Login to connect via SSH - + Login to connect via SSH Ip address cannot be empty - + Поле Ip address не может быть пустым Login cannot be empty - + Поле Login не может быть пустым Password/private key cannot be empty - + Поле Password/private key не может быть пустым @@ -1629,27 +1647,27 @@ It's okay as long as it's from someone you trust. What is the level of internet control in your region? - + Какой уровень контроля интеренета в вашем регионе? Set up a VPN yourself - + Настроить VPN самостоятельно I want to choose a VPN protocol - + Выбор VPN-протокола Continue - Продолжить + Продолжить Set up later - + Настроить позднее @@ -1657,33 +1675,33 @@ It's okay as long as it's from someone you trust. The server has already been added to the application - + Сервер уже был добавлен в приложение Amnesia has detected that your server is currently - + Amnesia обнаружила, что ваш сервер в настоящее время busy installing other software. Amnesia installation - + занят установкой других протоколов или сервисов. Установка Amnesia will pause until the server finishes installing other software - + будет приостановлена до тех пор, пока сервер не завершит установку Installing - + Установка Usually it takes no more than 5 minutes - + Обычно это занимает не более 5 минут @@ -1691,32 +1709,32 @@ It's okay as long as it's from someone you trust. Installing %1 - + Установка %1 More detailed - + Подробнее Close - + Закрыть Network protocol - + Сетевой протокол Port - + Порт Install - + Установка @@ -1724,12 +1742,12 @@ It's okay as long as it's from someone you trust. VPN protocol - + VPN протокол Choose the one with the highest priority for you. Later, you can install other protocols and additional services, such as DNS proxy and SFTP. - + Выберите протокол, который вам больше подходит . В дальнейшем можно установить другие протоколы и дополнительные сервисы, такие как DNS-прокси и SFTP. @@ -1737,7 +1755,7 @@ It's okay as long as it's from someone you trust. Point the camera at the QR code and hold for a couple of seconds. - + Наведите камеру на QR-код и удерживайте ее в течение нескольких секунд. @@ -1745,27 +1763,27 @@ It's okay as long as it's from someone you trust. Settings restored from backup file - + Восстановление настроек из бэкап файла Free service for creating a personal VPN on your server. - + Простое и бесплатное приложение для запуска self-hosted VPN с высокими требованиями к приватности. Helps you access blocked content without revealing your privacy, even to VPN providers. - + Помогает получить доступ к заблокированному контенту, не раскрывая вашу конфиденциальность даже провайдерам VPN. I have the data to connect - + У меня есть данные для подключения I have nothing - + У меня ничего нет @@ -1773,27 +1791,27 @@ It's okay as long as it's from someone you trust. Connection key - + Ключ для подключения A line that starts with vpn://... - + Строка, которая начинается с vpn://... Key - + Ключ Insert - + Вставка Continue - Продолжить + Продолжить @@ -1801,27 +1819,27 @@ It's okay as long as it's from someone you trust. New connection - + Новое соединение Do not use connection code from public sources. It could be created to intercept your data. - + Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватывать ваши данные. Collapse content - + Свернуть Show content - + Показать содержимое ключа Connect - + Подключиться @@ -1829,95 +1847,95 @@ It's okay as long as it's from someone you trust. OpenVpn native format - + OpenVpn нативный формат WireGuard native format - + WireGuard нативный формат VPN Access - + VPN-Доступ Connection - + Соединение VPN access without the ability to manage the server - + Доступ к VPN, без возможности управления сервером Server - + Сервер Accessing - + Доступ File with accessing settings to - + Файл с настройками доступа к Connection to - + Подключение к File with connection settings to - + Файл с настройками доступа к Save OpenVPN config - + Сохранить OpenVPN config Save WireGuard config - + Сохранить WireGuard config For the AmneziaVPN app - + Для AmneziaVPN Full access - + Полный доступ Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the servers, as well as change settings. - + Доступ к управлению серверами. Пользователь, с которым вы поделились полным доступ к VPN, сможет добавлять и удалять ваши протоколы и сервисы на сервере, а также менять настройки. Protocol - + Протокол Connection format - + Формат подключения Share - + Поделиться @@ -1925,7 +1943,7 @@ It's okay as long as it's from someone you trust. Close - + Закрыть @@ -1933,38 +1951,38 @@ It's okay as long as it's from someone you trust. Password entry not found - + Password entry not found Could not decrypt data - + Could not decrypt data Unknown error - + Unknown error Could not open wallet: %1; %2 - + Could not open wallet: %1; %2 Password not found - + Password not found Could not open keystore - + Could not open keystore Could not remove private key from keystore - + Could not remove private key from keystore @@ -1972,12 +1990,12 @@ It's okay as long as it's from someone you trust. Unknown error - + Unknown error Access to keychain denied - + Access to keychain denied @@ -1985,27 +2003,27 @@ It's okay as long as it's from someone you trust. Could not store data in settings: access error - + Could not store data in settings: access error Could not store data in settings: format error - + Could not store data in settings: format error Could not delete data from settings: access error - + Could not delete data from settings: access error Could not delete data from settings: format error - + Could not delete data from settings: format error Entry not found - + Entry not found @@ -2013,80 +2031,80 @@ It's okay as long as it's from someone you trust. Password entry not found - + Password entry not found Could not decrypt data - + Could not decrypt data D-Bus is not running - + D-Bus is not running Unknown error - + Unknown error No keychain service available - + No keychain service available Could not open wallet: %1; %2 - + Could not open wallet: %1; %2 Access to keychain denied - + Access to keychain denied Could not determine data type: %1; %2 - + Could not determine data type: %1; %2 Entry not found - + Entry not found Unsupported entry type 'Map' - + Unsupported entry type 'Map' Unknown kwallet entry type '%1' - + Unknown kwallet entry type '%1' Password not found - + Password not found Could not open keystore - + Could not open keystore Could not retrieve private key from keystore - + Could not retrieve private key from keystore Could not create decryption cipher - + Could not create decryption cipher @@ -2094,73 +2112,73 @@ It's okay as long as it's from someone you trust. Credential size exceeds maximum size of %1 - + Credential size exceeds maximum size of %1 Credential key exceeds maximum size of %1 - + Credential key exceeds maximum size of %1 Writing credentials failed: Win32 error code %1 - + Writing credentials failed: Win32 error code %1 Encryption failed - + Encryption failed D-Bus is not running - + D-Bus is not running Unknown error - + Unknown error Could not open wallet: %1; %2 - + Could not open wallet: %1; %2 Password not found - + Password not found Could not open keystore - + Could not open keystore Could not create private key generator - + Could not create private key generator Could not generate new private key - + Could not generate new private key Could not retrieve private key from keystore - + Could not retrieve private key from keystore Could not create encryption cipher - + Could not create encryption cipher Could not encrypt data - + Could not encrypt data @@ -2168,374 +2186,378 @@ It's okay as long as it's from someone you trust. No error - + No error Unknown Error - + Unknown Error Function not implemented - + Function not implemented Server check failed - + Server check failed Server port already used. Check for another software - + Server port already used. Check for another software Server error: Docker container missing - + Server error: Docker container missing Server error: Docker failed - + Server error: Docker failed Installation canceled by user - + Installation canceled by user The user does not have permission to use sudo - + The user does not have permission to use sudo Ssh request was denied - + Ssh request was denied Ssh request was interrupted - + Ssh request was interrupted Ssh internal error - + Ssh internal error Invalid private key or invalid passphrase entered - + Invalid private key or invalid passphrase entered The selected private key format is not supported, use openssh ED25519 key types or PEM key types - + The selected private key format is not supported, use openssh ED25519 key types or PEM key types Timeout connecting to server - + Timeout connecting to server Sftp error: End-of-file encountered - + Sftp error: End-of-file encountered Sftp error: File does not exist - + Sftp error: File does not exist Sftp error: Permission denied - + Sftp error: Permission denied Sftp error: Generic failure - + Sftp error: Generic failure Sftp error: Garbage received from server - + Sftp error: Garbage received from server Sftp error: No connection has been set up - + Sftp error: No connection has been set up Sftp error: There was a connection, but we lost it - + Sftp error: There was a connection, but we lost it Sftp error: Operation not supported by libssh yet - + Sftp error: Operation not supported by libssh yet Sftp error: Invalid file handle - + Sftp error: Invalid file handle Sftp error: No such file or directory path exists - + Sftp error: No such file or directory path exists Sftp error: An attempt to create an already existing file or directory has been made - + Sftp error: An attempt to create an already existing file or directory has been made Sftp error: Write-protected filesystem - + Sftp error: Write-protected filesystem Sftp error: No media was in remote drive - + Sftp error: No media was in remote drive Failed to save config to disk - + Failed to save config to disk OpenVPN config missing - + OpenVPN config missing OpenVPN management server error - + OpenVPN management server error OpenVPN executable missing - + OpenVPN executable missing ShadowSocks (ss-local) executable missing - + ShadowSocks (ss-local) executable missing Cloak (ck-client) executable missing - + Cloak (ck-client) executable missing Amnezia helper service error - + Amnezia helper service error OpenSSL failed - + OpenSSL failed Can't connect: another VPN connection is active - + Can't connect: another VPN connection is active Can't setup OpenVPN TAP network adapter - + Can't setup OpenVPN TAP network adapter VPN pool error: no available addresses - + VPN pool error: no available addresses The config does not contain any containers and credentiaks for connecting to the server - + The config does not contain any containers and credentiaks for connecting to the server Internal error - + Internal error IPsec - + IPsec DNS Service - + DNS Сервис Sftp file sharing service - + Сервис обмена файлами Sftp Website in Tor network - + Веб-сайт в сети Tor Amnezia DNS - + Amnezia DNS OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its own security protocol with SSL/TLS for key exchange. - + OpenVPN - популярный VPN-протокол, с гибкой настройкой. Имеет собственный протокол безопасности с SSL/TLS для обмена ключами. ShadowSocks - masks VPN traffic, making it similar to normal web traffic, but is recognised by analysis systems in some highly censored regions. - + ShadowSocks - маскирует VPN-трафик под обычный веб-трафик, но распознается системами анализа в некоторых регионах с высоким уровнем цензуры. OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against active-probbing detection. Ideal for bypassing blocking in regions with the highest levels of censorship. - + OpenVPN over Cloak - OpenVPN с маскировкой VPN под web-трафик и защитой от обнаружения active-probbing. Подходит для регионов с самым высоким уровнем цензуры. - WireGuard - New popular VPN protocol with high performance, high speed and low power consumption. Recommended for regions with low levels of censorship. + WireGuard - Популярный VPN-протокол с высокой производительностью, высокой скоростью и низким энергопотреблением. Для регионов с низким уровнем цензуры. + + + + AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, but very resistant to blockages. Recommended for regions with high levels of censorship. IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS. - + IKEv2 Современный стабильный протокол, немного быстрее других восстанавливает соединение после потери сигнала. Имеет нативную поддержку последних версиий Android и iOS. Deploy a WordPress site on the Tor network in two clicks. - + Разверните сайт на WordPress в сети Tor в два клика. Replace the current DNS server with your own. This will increase your privacy level. - + Замените адрес DNS-сервера на собственный. Это повысит уровень конфиденциальности. Creates a file vault on your server to securely store and transfer files. - + Создайте на сервере файловое хранилище для безопасного хранения и передачи файлов. OpenVPN container - + OpenVPN протокол Container with OpenVpn and ShadowSocks - + Связка протоколов OpenVPN и ShadowSocks Container with OpenVpn and ShadowSocks protocols configured with traffic masking by Cloak plugin - + Протоколы OpenVpn и ShadowSocks, с плагином маскировки трафика Cloak WireGuard container - + WireGuard протокол AmneziaWG container - + AmneziaWG протокол IPsec container - + IPsec протокол Sftp file sharing service - is secure FTP service - + Сервис обмена файлами Sftp - безопасный FTP-сервис Sftp service - + Сервис SFTP Entry not found - + Entry not found Access to keychain denied - + Access to keychain denied No keyring daemon - + No keyring daemon Already unlocked - + Already unlocked No such keyring - + No such keyring Bad arguments - + Bad arguments I/O error - + I/O error Cancelled - + Cancelled Keyring already exists - + Keyring already exists No match - + No match Unknown error - + Unknown error error 0x%1: %2 - + error 0x%1: %2 @@ -2543,7 +2565,7 @@ It's okay as long as it's from someone you trust. Choose language - + Выберете язык @@ -2551,13 +2573,13 @@ It's okay as long as it's from someone you trust. Server #1 - + Server #1 Server - + Server @@ -2565,22 +2587,22 @@ It's okay as long as it's from someone you trust. Software version - + Версия ПО All settings have been reset to default values - + Все настройки были сброшены к значению "По умолчанию" Cached profiles cleared - + Кэш профиля очищен Backup file is corrupted - + Backup файл поврежден @@ -2589,32 +2611,32 @@ It's okay as long as it's from someone you trust. Save AmneziaVPN config - + Сохранить config AmneziaVPN Share - + Поделиться Copy - + Скопировать Copied - + Скопировано Show connection settings - + Показать настройки подключения To read the QR code in the Amnezia app, select "Add server" → "I have data to connect" → "QR code, key or settings file" - + Для считывания QR-кода в приложении Amnezia выберите "Добавить сервер" → "У меня есть данные для подключения" → "QR-код, ключ или файл настроек" @@ -2622,42 +2644,42 @@ It's okay as long as it's from someone you trust. Hostname not look like ip adress or domain name - + Имя хоста не похоже на ip-адрес или доменное имя New site added: %1 - + Добавлен новый сайт %1 Site removed: %1 - + Сайт удален %1 Can't open file: %1 - + Невозможно открыть файл: %1 Failed to parse JSON data from file: %1 - + Не удалось разобрать JSON-данные из файла: %1 The JSON data is not an array in file: %1 - + Данные JSON не являются массивом в файле: %1 Import completed - + Импорт завершен Export completed - + Экспорт завершен @@ -2666,31 +2688,31 @@ It's okay as long as it's from someone you trust. Show - + Показать Connect - + Подключиться Disconnect - + Отключиться Visit Website - + Посетить сайт Quit - + Закрыть @@ -2698,7 +2720,7 @@ It's okay as long as it's from someone you trust. The field can't be empty - + Поле не может быть пустым @@ -2706,7 +2728,7 @@ It's okay as long as it's from someone you trust. Mbps - + Mbps @@ -2714,42 +2736,42 @@ It's okay as long as it's from someone you trust. Unknown - + Неизвестный Disconnected - + Отключен Preparing - + Подготовка Connecting... - + Подключение... Connected - + Подключено Disconnecting... - + Отключение... Reconnecting... - + Переподключение... Error - + Ошибка @@ -2757,45 +2779,65 @@ It's okay as long as it's from someone you trust. Low - + Низкий - High + Medium or High - Medium - - - - - Many foreign websites and VPN providers are blocked - - - - - Some foreign sites are blocked, but VPN providers are not blocked + Extreme - I just want to increase the level of privacy + I just want to increase the level of my privacy. + + + I want to bypass censorship. This option recommended in most cases. + + + + + Most VPN protocols are blocked. Recommended if other options are not working. + + + + High + Высокий + + + Medium + Средний + + + Many foreign websites and VPN providers are blocked + Многие иностранные сайты и VPN-провайдеры заблокированы + + + Some foreign sites are blocked, but VPN providers are not blocked + Некоторые иностранные сайты заблокированы, но VPN-провайдеры не блокируются + + + I just want to increase the level of privacy + Хочу просто повысить уровень приватности + main2 Private key passphrase - + Кодовая фраза для закрытого ключа Save - + Сохранить From 384ce9853b92b99f060a566257f1e0b701f9272d Mon Sep 17 00:00:00 2001 From: ronoaer Date: Sat, 14 Oct 2023 23:00:31 +0800 Subject: [PATCH 220/278] added new drawer2type for replacing drawertype --- client/resources.qrc | 1 + .../ConnectionTypeSelectionDrawer.qml | 2 +- client/ui/qml/Components/QuestionDrawer.qml | 2 +- .../qml/Components/SelectLanguageDrawer.qml | 2 +- .../qml/Components/ShareConnectionDrawer.qml | 11 +- client/ui/qml/Controls2/Drawer2Type.qml | 266 ++++++++++++++++++ client/ui/qml/Controls2/DropDownType.qml | 5 +- .../Pages2/PageProtocolOpenVpnSettings.qml | 7 +- client/ui/qml/Pages2/PageProtocolRaw.qml | 10 +- .../ui/qml/Pages2/PageServiceDnsSettings.qml | 7 +- .../ui/qml/Pages2/PageServiceSftpSettings.qml | 7 +- .../Pages2/PageServiceTorWebsiteSettings.qml | 7 +- .../ui/qml/Pages2/PageSettingsApplication.qml | 8 +- client/ui/qml/Pages2/PageSettingsBackup.qml | 6 +- client/ui/qml/Pages2/PageSettingsDns.qml | 7 +- client/ui/qml/Pages2/PageSettingsLogging.qml | 7 +- .../ui/qml/Pages2/PageSettingsServerData.qml | 12 +- .../ui/qml/Pages2/PageSettingsServerInfo.qml | 6 +- .../qml/Pages2/PageSettingsServerProtocol.qml | 6 +- .../qml/Pages2/PageSettingsSplitTunneling.qml | 15 +- .../PageSetupWizardProtocolSettings.qml | 4 +- client/ui/qml/Pages2/PageSetupWizardStart.qml | 3 +- client/ui/qml/Pages2/PageShare.qml | 1 + client/ui/qml/Pages2/PageStart.qml | 4 +- 24 files changed, 350 insertions(+), 56 deletions(-) create mode 100644 client/ui/qml/Controls2/Drawer2Type.qml diff --git a/client/resources.qrc b/client/resources.qrc index f0494852..d7f8ff7a 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -216,5 +216,6 @@ ui/qml/Pages2/PageServiceDnsSettings.qml ui/qml/Controls2/TopCloseButtonType.qml images/controls/x-circle.svg + ui/qml/Controls2/Drawer2Type.qml diff --git a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml index ecde1554..a368d45c 100644 --- a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml +++ b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml @@ -8,7 +8,7 @@ import "../Controls2" import "../Controls2/TextTypes" import "../Config" -DrawerType { +Drawer2Type { id: root width: parent.width diff --git a/client/ui/qml/Components/QuestionDrawer.qml b/client/ui/qml/Components/QuestionDrawer.qml index a79f9140..06f4ae24 100644 --- a/client/ui/qml/Components/QuestionDrawer.qml +++ b/client/ui/qml/Components/QuestionDrawer.qml @@ -5,7 +5,7 @@ import QtQuick.Layouts import "../Controls2" import "../Controls2/TextTypes" -DrawerType { +Drawer2Type { id: root property string headerText diff --git a/client/ui/qml/Components/SelectLanguageDrawer.qml b/client/ui/qml/Components/SelectLanguageDrawer.qml index d318aab8..f27fce6c 100644 --- a/client/ui/qml/Components/SelectLanguageDrawer.qml +++ b/client/ui/qml/Components/SelectLanguageDrawer.qml @@ -5,7 +5,7 @@ import QtQuick.Layouts import "../Controls2" import "../Controls2/TextTypes" -DrawerType { +Drawer2Type { id: root width: parent.width diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 1158dadc..cb74f42e 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -16,7 +16,7 @@ import "../Controls2/TextTypes" import "../Config" import "../Components" -DrawerType { +Drawer2Type { id: root property alias headerText: header.headerText @@ -30,7 +30,7 @@ DrawerType { width: parent.width height: parent.height * 0.9 - onClosed: { + onClose: { configExtension = ".vpn" configCaption = qsTr("Save AmneziaVPN config") configFileName = "amnezia_config" @@ -126,13 +126,14 @@ DrawerType { text: qsTr("Show connection settings") onClicked: { - configContentDrawer.visible = true + configContentDrawer.open() } } - DrawerType { + Drawer2Type { id: configContentDrawer + parent: root width: parent.width height: parent.height * 0.9 @@ -145,7 +146,7 @@ DrawerType { anchors.topMargin: 16 backButtonFunction: function() { - configContentDrawer.visible = false + configContentDrawer.close() } } diff --git a/client/ui/qml/Controls2/Drawer2Type.qml b/client/ui/qml/Controls2/Drawer2Type.qml new file mode 100644 index 00000000..eae28846 --- /dev/null +++ b/client/ui/qml/Controls2/Drawer2Type.qml @@ -0,0 +1,266 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Shapes + +Item { + id: root + + Connections { + target: PageController + + function onForceCloseDrawer() { + root.state = "closed" + } + } + + visible: false + + parent: Overlay.overlay + + signal close() + + property bool needCloseButton: true + + property string defaultColor: "#1C1D21" + property string borderColor: "#2C2D30" + property int collapsedHeight: 0 + + property bool needCollapsed: false + + + y: parent.height - root.height + + state: "closed" + + Rectangle { + id: draw2Background + + anchors { left: root.left; right: root.right; top: root.top } + height: root.height + radius: 16 + color: root.defaultColor + border.color: root.borderColor + border.width: 1 + + Rectangle { + width: parent.radius + height: parent.radius + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.left: parent.left + color: parent.color + } + } + + MouseArea { + anchors.fill: parent + enabled: (root.state === "expanded" || root.state === "opened") + onClicked: { + if (root.state === "expanded") { + root.state = "collapsed" + return + } + + if (root.state === "opened") { + root.state = "closed" + return + } + } + } + + Drag.active: dragArea.drag.active + + MouseArea { + id: dragArea + + anchors.fill: parent + cursorShape: (root.state === "opened" || root.state === "expanded") ? Qt.PointingHandCursor : Qt.ArrowCursor + hoverEnabled: true + + drag.target: parent + drag.axis: Drag.YAxis + drag.maximumY: parent.height + drag.minimumY: parent.height - root.height + + /** If drag area is released at any point other than min or max y, transition to the other state */ + onReleased: { + if (root.state === "closed" && root.y < dragArea.drag.maximumY) { + root.state = "opened" + PageController.drawerOpen() + return + } + + if (root.state === "opened" && root.y > dragArea.drag.minimumY) { + root.state = "closed" + PageController.drawerClose() + return + } + + if (root.state === "collapsed" && root.y < dragArea.drag.maximumY) { + root.state = "expanded" + return + } + if (root.state === "expanded" && root.y > dragArea.drag.minimumY) { + root.state = "collapsed" + return + } + } + + onClicked: { + if (root.state === "opened") { + root.state = "closed" + } + + if (root.state == "expanded") { + root.state == "collapsed" + } + } + } + + onStateChanged: { + if (root.state === "closed" || root.state === "collapsed") { + var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor() + if (initialPageNavigationBarColor !== 0xFF1C1D21) { + PageController.updateNavigationBarColor(initialPageNavigationBarColor) + } + + PageController.drawerClose() + return + } + if (root.state === "expanded" || root.state === "opened") { + if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) { + PageController.updateNavigationBarColor(0xFF1C1D21) + } + PageController.drawerOpen() + return + } + } + + /** Two states of buttonContent, great place to add any future animations for the drawer */ + states: [ + State { + name: "collapsed" + PropertyChanges { + target: root + y: root.height - collapsedHeight + } + }, + State { + name: "expanded" + PropertyChanges { + target: root + y: dragArea.drag.minimumY + } + }, + + State { + name: "closed" + PropertyChanges { + target: root + y: parent.height + } + }, + + State { + name: "opend" + PropertyChanges { + target: root + y: dragArea.drag.minimumY + } + } + ] + + transitions: [ + Transition { + from: "collapsed" + to: "expanded" + PropertyAnimation { + target: root + properties: "y" + duration: 200 + } + }, + Transition { + from: "expanded" + to: "collapsed" + PropertyAnimation { + target: root + properties: "y" + duration: 200 + } + }, + + Transition { + from: "opened" + to: "closed" + PropertyAnimation { + target: root + properties: "y" + duration: 200 + } + }, + + Transition { + from: "closed" + to: "opened" + PropertyAnimation { + target: root + properties: "y" + duration: 200 + } + } + ] + + NumberAnimation { + id: animationVisible + target: root + property: "y" + from: parent.height + to: parent.height - root.height + duration: 200 + } + + function open() { + if (root.visible && root.state !== "closed") { + return + } + + root.state = "opened" + root.visible = true + root.y = parent.height - root.height + + dragArea.drag.maximumY = parent.height + dragArea.drag.minimumY = parent.height - root.height + + + animationVisible.running = true + + if (needCloseButton) { + PageController.drawerOpen() + } + + if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) { + PageController.updateNavigationBarColor(0xFF1C1D21) + } + } + + function onClose() { + if (needCloseButton) { + PageController.drawerClose() + } + + var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor() + if (initialPageNavigationBarColor !== 0xFF1C1D21) { + PageController.updateNavigationBarColor(initialPageNavigationBarColor) + } + + root.visible = false + } + + onVisibleChanged: { + if (!visible) { + close() + } + } +} diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index b91f0b7a..cab2ef4e 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -156,17 +156,20 @@ Item { if (rootButtonClickedFunction && typeof rootButtonClickedFunction === "function") { rootButtonClickedFunction() } else { - menu.visible = true + menu.open() } } } + // Drawer2Type { DrawerType { id: menu width: parent.width height: parent.height * drawerHeight + // parent: root.parent.parent + ColumnLayout { id: header diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index c8469dcb..f03cfc07 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -365,14 +365,14 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.visible = false + questionDrawer.onClose() PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeCurrentlyProcessedContainer() } questionDrawer.noButtonFunction = function() { - questionDrawer.visible = false + questionDrawer.onClose() } - questionDrawer.visible = true + questionDrawer.open() } } @@ -397,6 +397,7 @@ PageType { QuestionDrawer { id: questionDrawer + parent: root } } } diff --git a/client/ui/qml/Pages2/PageProtocolRaw.qml b/client/ui/qml/Pages2/PageProtocolRaw.qml index 2324c091..3e0fe5ab 100644 --- a/client/ui/qml/Pages2/PageProtocolRaw.qml +++ b/client/ui/qml/Pages2/PageProtocolRaw.qml @@ -90,8 +90,9 @@ PageType { DividerType {} - DrawerType { + Drawer2Type { id: configContentDrawer + parent: root width: parent.width height: parent.height * 0.9 @@ -179,14 +180,14 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.visible = false + questionDrawer.onClose() PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeCurrentlyProcessedContainer() } questionDrawer.noButtonFunction = function() { - questionDrawer.visible = false + questionDrawer.onClose() } - questionDrawer.visible = true + questionDrawer.open() } MouseArea { @@ -201,6 +202,7 @@ PageType { QuestionDrawer { id: questionDrawer + parent: root } } } diff --git a/client/ui/qml/Pages2/PageServiceDnsSettings.qml b/client/ui/qml/Pages2/PageServiceDnsSettings.qml index 10fe6f56..70aba8e2 100644 --- a/client/ui/qml/Pages2/PageServiceDnsSettings.qml +++ b/client/ui/qml/Pages2/PageServiceDnsSettings.qml @@ -68,14 +68,14 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.visible = false + questionDrawer.onClose() PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeCurrentlyProcessedContainer() } questionDrawer.noButtonFunction = function() { - questionDrawer.visible = false + questionDrawer.onClose() } - questionDrawer.visible = true + questionDrawer.open() } MouseArea { @@ -89,6 +89,7 @@ PageType { QuestionDrawer { id: questionDrawer + parent: root } } } diff --git a/client/ui/qml/Pages2/PageServiceSftpSettings.qml b/client/ui/qml/Pages2/PageServiceSftpSettings.qml index 61ba663d..0af47cdd 100644 --- a/client/ui/qml/Pages2/PageServiceSftpSettings.qml +++ b/client/ui/qml/Pages2/PageServiceSftpSettings.qml @@ -265,14 +265,14 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.visible = false + questionDrawer.onClose() PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeCurrentlyProcessedContainer() } questionDrawer.noButtonFunction = function() { - questionDrawer.visible = false + questionDrawer.onClose() } - questionDrawer.visible = true + questionDrawer.open() } } } @@ -282,6 +282,7 @@ PageType { QuestionDrawer { id: questionDrawer + parent: root } } } diff --git a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml index 04d7076c..deff35eb 100644 --- a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml +++ b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml @@ -143,20 +143,21 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.visible = false + questionDrawer.onClose() PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeCurrentlyProcessedContainer() } questionDrawer.noButtonFunction = function() { - questionDrawer.visible = false + questionDrawer.onClose() } - questionDrawer.visible = true + questionDrawer.open() } } } QuestionDrawer { id: questionDrawer + parent: root } } } diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index c5536fdb..225e2b14 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -119,6 +119,7 @@ PageType { SelectLanguageDrawer { id: selectLanguageDrawer + parent: root } @@ -151,14 +152,14 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.visible = false + questionDrawer.onClose() SettingsController.clearSettings() PageController.replaceStartPage() } questionDrawer.noButtonFunction = function() { - questionDrawer.visible = false + questionDrawer.onClose() } - questionDrawer.visible = true + questionDrawer.open() } } @@ -166,6 +167,7 @@ PageType { QuestionDrawer { id: questionDrawer + parent: root } } } diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml index 96214893..61370dab 100644 --- a/client/ui/qml/Pages2/PageSettingsBackup.qml +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -138,15 +138,15 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.visible = false + questionDrawer.onClose() PageController.showBusyIndicator(true) SettingsController.restoreAppConfig(filePath) PageController.showBusyIndicator(false) } questionDrawer.noButtonFunction = function() { - questionDrawer.visible = false + questionDrawer.onClose() } - questionDrawer.visible = true + questionDrawer.open() } QuestionDrawer { diff --git a/client/ui/qml/Pages2/PageSettingsDns.qml b/client/ui/qml/Pages2/PageSettingsDns.qml index 58ec0783..917c6992 100644 --- a/client/ui/qml/Pages2/PageSettingsDns.qml +++ b/client/ui/qml/Pages2/PageSettingsDns.qml @@ -91,7 +91,7 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.visible = false + questionDrawer.onClose() SettingsController.primaryDns = "1.1.1.1" primaryDns.textFieldText = SettingsController.primaryDns SettingsController.secondaryDns = "1.0.0.1" @@ -99,9 +99,9 @@ PageType { PageController.showNotificationMessage(qsTr("Settings have been reset")) } questionDrawer.noButtonFunction = function() { - questionDrawer.visible = false + questionDrawer.onClose() } - questionDrawer.visible = true + questionDrawer.open() } } @@ -123,6 +123,7 @@ PageType { } QuestionDrawer { id: questionDrawer + parent: root } } } diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml index 4141f51f..1532d1dc 100644 --- a/client/ui/qml/Pages2/PageSettingsLogging.qml +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -146,16 +146,16 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.visible = false + questionDrawer.onClose() PageController.showBusyIndicator(true) SettingsController.clearLogs() PageController.showBusyIndicator(false) PageController.showNotificationMessage(qsTr("Logs have been cleaned up")) } questionDrawer.noButtonFunction = function() { - questionDrawer.visible = false + questionDrawer.onClose() } - questionDrawer.visible = true + questionDrawer.open() } } @@ -171,6 +171,7 @@ PageType { QuestionDrawer { id: questionDrawer + parent: root } } } diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 3eb07ce9..c90e66b3 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -94,15 +94,15 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.visible = false + questionDrawer.onClose() PageController.showBusyIndicator(true) SettingsController.clearCachedProfiles() PageController.showBusyIndicator(false) } questionDrawer.noButtonFunction = function() { - questionDrawer.visible = false + questionDrawer.onClose() } - questionDrawer.visible = true + questionDrawer.open() } } @@ -172,7 +172,7 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.visible = false + questionDrawer.onClose() PageController.goToPage(PageEnum.PageDeinstalling) if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { ConnectionController.closeConnection() @@ -180,9 +180,9 @@ PageType { InstallController.removeAllContainers() } questionDrawer.noButtonFunction = function() { - questionDrawer.visible = false + questionDrawer.onClose() } - questionDrawer.visible = true + questionDrawer.open() } } diff --git a/client/ui/qml/Pages2/PageSettingsServerInfo.qml b/client/ui/qml/Pages2/PageSettingsServerInfo.qml index e2e7868c..d534b509 100644 --- a/client/ui/qml/Pages2/PageSettingsServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsServerInfo.qml @@ -71,13 +71,14 @@ PageType { } actionButtonFunction: function() { - serverNameEditDrawer.visible = true + serverNameEditDrawer.open() } } - DrawerType { + Drawer2Type { id: serverNameEditDrawer + parent: root width: root.width height: root.height * 0.35 @@ -95,6 +96,7 @@ PageType { anchors.leftMargin: 16 anchors.rightMargin: 16 + TextFieldWithHeaderType { id: serverName diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml index 30758da4..594e4520 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -119,14 +119,14 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.visible = false + questionDrawer.onClose() PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeCurrentlyProcessedContainer() } questionDrawer.noButtonFunction = function() { - questionDrawer.visible = false + questionDrawer.onClose() } - questionDrawer.visible = true + questionDrawer.open() } MouseArea { diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index b79d5d22..406fae66 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -200,13 +200,13 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.visible = false + questionDrawer.onClose() SitesController.removeSite(index) } questionDrawer.noButtonFunction = function() { - questionDrawer.visible = false + questionDrawer.onClose() } - questionDrawer.visible = true + questionDrawer.open() } } @@ -214,6 +214,7 @@ PageType { QuestionDrawer { id: questionDrawer + parent: root } } } @@ -259,12 +260,14 @@ PageType { } } - DrawerType { + Drawer2Type { id: moreActionsDrawer width: parent.width height: parent.height * 0.4375 + parent: root + FlickableType { anchors.fill: parent contentHeight: moreActionsDrawerContent.height @@ -324,12 +327,14 @@ PageType { } } - DrawerType { + Drawer2Type { id: importSitesDrawer width: parent.width height: parent.height * 0.4375 + parent: root + BackButtonType { id: importSitesDrawerBackButton diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 7535464a..794ec90b 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -97,12 +97,14 @@ PageType { } } - DrawerType { + Drawer2Type { id: showDetailsDrawer width: parent.width height: parent.height * 0.9 + parent: root + BackButtonType { id: showDetailsBackButton diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index 9f5e57a5..17aa860f 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -115,7 +115,7 @@ PageType { text: qsTr("I have the data to connect") onClicked: { - connectionTypeSelection.visible = true + connectionTypeSelection.open() } } @@ -140,6 +140,7 @@ PageType { ConnectionTypeSelectionDrawer { id: connectionTypeSelection + parent: root } } diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 2fa5e4a1..6c641ea6 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -371,6 +371,7 @@ PageType { ShareConnectionDrawer { id: shareConnectionDrawer + parent: root } BasicButtonType { diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 4af774fa..923d1e79 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -135,6 +135,8 @@ PageType { var pagePath = PageController.getPagePath(PageEnum.PageHome) ServersModel.currentlyProcessedIndex = ServersModel.defaultIndex tabBarStackView.push(pagePath, { "objectName" : pagePath }) + + connectionTypeSelection.parent = tabBarStackView } onWidthChanged: { @@ -243,7 +245,7 @@ PageType { ConnectionTypeSelectionDrawer { id: connectionTypeSelection - onAboutToHide: { + onClose: function() { tabBar.setCurrentIndex(tabBar.previousIndex) } } From 512ac74ee6f1201344fc2b89ce2244e36c758428 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sat, 14 Oct 2023 20:59:03 +0500 Subject: [PATCH 221/278] temporarily disabled the drawer close button --- client/protocols/protocols_defs.cpp | 2 +- client/translations/amneziavpn_ru.ts | 8 ++++---- client/translations/amneziavpn_zh_CN.ts | 8 ++++---- client/ui/qml/Pages2/PageStart.qml | 25 +++++++++++++------------ 4 files changed, 22 insertions(+), 21 deletions(-) diff --git a/client/protocols/protocols_defs.cpp b/client/protocols/protocols_defs.cpp index b7f6b1d8..5964bd87 100644 --- a/client/protocols/protocols_defs.cpp +++ b/client/protocols/protocols_defs.cpp @@ -66,7 +66,7 @@ QMap ProtocolProps::protocolHumanNames() { Proto::ShadowSocks, "ShadowSocks" }, { Proto::Cloak, "Cloak" }, { Proto::WireGuard, "WireGuard" }, - { Proto::WireGuard, "AmneziaWG" }, + { Proto::Awg, "AmneziaWG" }, { Proto::Ikev2, "IKEv2" }, { Proto::L2tp, "L2TP" }, diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index 94ce0b3a..73a7dead 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -1704,22 +1704,22 @@ It's okay as long as it's from someone you trust. - + Close - + Network protocol - + Port - + Install diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index 359655f8..2e772823 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -1807,22 +1807,22 @@ It's okay as long as it's from someone you trust. 更多细节 - + Close 关闭 - + Network protocol 网络协议 - + Port 端口 - + Install 安装 diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 4af774fa..ab02ace4 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -44,9 +44,9 @@ PageType { tabBar.enabled = !visible } - function onShowTopCloseButton(visible) { - topCloseButton.visible = visible - } +// function onShowTopCloseButton(visible) { +// topCloseButton.visible = visible +// } function onEnableTabBar(enabled) { tabBar.enabled = enabled @@ -137,10 +137,10 @@ PageType { tabBarStackView.push(pagePath, { "objectName" : pagePath }) } - onWidthChanged: { - topCloseButton.x = tabBarStackView.x + tabBarStackView.width - - topCloseButton.buttonWidth - topCloseButton.rightPadding - } +// onWidthChanged: { +// topCloseButton.x = tabBarStackView.x + tabBarStackView.width - +// topCloseButton.buttonWidth - topCloseButton.rightPadding +// } } TabBar { @@ -234,11 +234,12 @@ PageType { z: 1 } - TopCloseButtonType { - id: topCloseButton - x: tabBarStackView.width - topCloseButton.buttonWidth - topCloseButton.rightPadding - z: 1 - } +// TopCloseButtonType { +// id: topCloseButton + +// x: tabBarStackView.width - topCloseButton.buttonWidth - topCloseButton.rightPadding +// z: 1 +// } ConnectionTypeSelectionDrawer { id: connectionTypeSelection From 8885f580b29ef6424df1793aecd8f48a6daefb42 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sat, 14 Oct 2023 21:18:38 +0500 Subject: [PATCH 222/278] added notification after saving backup and logs files --- client/ui/qml/Pages2/PageSettingsBackup.qml | 1 + client/ui/qml/Pages2/PageSettingsLogging.qml | 1 + 2 files changed, 2 insertions(+) diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml index 96214893..81be0465 100644 --- a/client/ui/qml/Pages2/PageSettingsBackup.qml +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -103,6 +103,7 @@ PageType { PageController.showBusyIndicator(true) SettingsController.backupAppConfig(fileName) PageController.showBusyIndicator(false) + PageController.showNotificationMessage(qsTr("Backup file saved")) } } } diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml index 4141f51f..840c41d4 100644 --- a/client/ui/qml/Pages2/PageSettingsLogging.qml +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -115,6 +115,7 @@ PageType { PageController.showBusyIndicator(true) SettingsController.exportLogsFile(fileName) PageController.showBusyIndicator(false) + PageController.showNotificationMessage(qsTr("Logs file saved")) } } } From f65e4066e3911c1f28b1b6a2973e8f01327880db Mon Sep 17 00:00:00 2001 From: pokamest Date: Sat, 14 Oct 2023 23:59:46 +0100 Subject: [PATCH 223/278] ui fixes --- client/CMakeLists.txt | 1 - client/translations/amneziavpn_ru.ts | 164 +++++++++--------- client/translations/amneziavpn_zh_CN.ts | 127 ++++++++------ .../ConnectionTypeSelectionDrawer.qml | 5 +- client/ui/qml/Config/GlobalConfig.qml | 12 ++ .../ui/qml/Pages2/PageServiceSftpSettings.qml | 20 +-- .../Pages2/PageServiceTorWebsiteSettings.qml | 16 +- client/ui/qml/Pages2/PageSettings.qml | 4 +- .../ui/qml/Pages2/PageSettingsConnection.qml | 1 - 9 files changed, 171 insertions(+), 179 deletions(-) diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index bcefd30f..6c4f1ae4 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -74,7 +74,6 @@ qt6_add_resources(QRC ${I18NQRC} ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc) # -- i18n end if(IOS) - #execute_process(COMMAND bash ${CMAKE_CURRENT_LIST_DIR}/scripts/run-build-cloak.sh) execute_process(COMMAND bash ${CMAKE_CURRENT_LIST_DIR}/ios/scripts/openvpn.sh args WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) endif() diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index df2e53eb..a6c0464d 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -70,23 +70,18 @@ ConnectionTypeSelectionDrawer - Add server - + Add new connection + Добавить новое соединение - - Select data type - + + Configure your server + Настроить ваш сервер - - Server IP, login and password - - - - - QR code, key or configuration file - + + Open config file, key or QR code + Открыть файл конфига, ключ или QR код @@ -736,49 +731,49 @@ Already installed containers were found on the server. All installed containers Пароль - + Mount folder on device Смонтировать папку на вашем устройстве - + In order to mount remote SFTP folder as local drive, perform following steps: <br> Чтобы смонтировать SFTP-папку как локальный диск на вашем устройстве, выполните следующие действия - - + + <br>1. Install the latest version of <br>1. Установите последнюю версию - - + + <br>2. Install the latest version of <br>2. Установите последнюю версию - + Detailed instructions Подробные инструкции - + Remove SFTP and all data stored there Удалите SFTP-хранилище со всеми данными - + Remove SFTP and all data stored there? Удалить SFTP-хранилище и все хранящиеся на нем данные? - + Continue Продолжить - + Cancel Отменить @@ -806,37 +801,41 @@ Already installed containers were found on the server. All installed containers Скопировано - + Use <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor Browser</a> to open this url. Используйте <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor Browser</a> для открытия этой ссылки. - + After installation it takes several minutes while your onion site will become available in the Tor Network. Через несколько минут после установки ваш Onion сайт станет доступен в сети Tor. - - When configuring WordPress set the this address as domain. - + + When configuring WordPress set the this onion address as domain. + При настройке WordPress укажите этот onion адрес в качестве домена. - + When configuring WordPress set the this address as domain. + При настройке WordPress укажите этот onion адрес в качестве домена. + + + Remove website Удалить сайт - + The site with all data will be removed from the tor network. Сайт со всеми данными будет удален из сети tor. - + Continue Продолжить - + Cancel Отменить @@ -1079,37 +1078,42 @@ Already installed containers were found on the server. All installed containers - + Backup files (*.backup) Файлы резервного копирования (*.backup) - + + Backup file saved + + + + Restore from backup Восстановить из бэкапа - + Open backup file Открыть бэкап файл - + Import settings from a backup file? Импортировать настройки из бэкап файла? - + All current settings will be reset Все текущие настройки будут сброшены - + Continue Продолжить - + Cancel Отменить @@ -1162,12 +1166,12 @@ Already installed containers were found on the server. All installed containers Позволяет подключаться к одним сайтам через VPN, а к другим в обход него - + App-based split tunneling Раздельное VPN-туннелирование приложений - + Allows you to use the VPN only for certain applications Позволяет использовать VPN только для определённых приложений @@ -1258,32 +1262,37 @@ Already installed containers were found on the server. All installed containers Logs files (*.log) - + + Logs file saved + + + + Save logs to file Сохранить логи в файл - + Clear logs? Очистить логи? - + Continue Продолжить - + Cancel Отменить - + Logs have been cleaned up Логи удалены - + Clear logs Удалить логи @@ -1585,9 +1594,8 @@ It's okay as long as it's from someone you trust. PageSetupWizardCredentials - Server connection - Подключение к серверу + Подключение к серверу @@ -1610,7 +1618,13 @@ It's okay as long as it's from someone you trust. Продолжить - + + All data you enter will remain strictly confidential +and will not be shared or disclosed to the Amnezia or any third parties + Все данные, которые вы вводите, останутся строго конфиденциальными и не будут переданы или раскрыты Amnezia или каким-либо третьим сторонам + + + Enter the address in the format 255.255.255.255:88 Введите адрес в формате 255.255.255.255:88 @@ -1620,17 +1634,22 @@ It's okay as long as it's from someone you trust. Login to connect via SSH - + + Configure your server + Настроить ваш сервер + + + Ip address cannot be empty Поле Ip address не может быть пустым - + Login cannot be empty Поле Login не может быть пустым - + Password/private key cannot be empty Поле Password/private key не может быть пустым @@ -1865,7 +1884,7 @@ It's okay as long as it's from someone you trust. Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the server, as well as change settings. - + Доступ к управлению сервером. Пользователь, с которым вы делитесь полным доступом к соединению, сможет добавлять и удалять ваши протоколы и службы на сервере, а также изменять настройки. @@ -2499,11 +2518,6 @@ IKEv2 with IPSec encryption layer. Transmits data over fixed UDP ports 500 and 4 AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, but very resistant to blockages. Recommended for regions with high levels of censorship. - - - AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, but very resistant to blockages. Recommended for regions with high levels of censorship. - - IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS. @@ -2599,16 +2613,6 @@ IKEv2 with IPSec encryption layer. Transmits data over fixed UDP ports 500 and 4 error 0x%1: %2 error 0x%1: %2 - - - WireGuard Configuration Highlighter - - - - - &Randomize colors - - SelectLanguageDrawer @@ -2834,37 +2838,27 @@ IKEv2 with IPSec encryption layer. Transmits data over fixed UDP ports 500 and 4 Medium or High - + Спедний или Высокий Extreme - + Экстремальный I just want to increase the level of my privacy. - + Я просто хочу повысить уровень своей приватности. I want to bypass censorship. This option recommended in most cases. - + Я хочу обойти блокировки. Этот вариант рекомендуется в большинстве случаев. Most VPN protocols are blocked. Recommended if other options are not working. - - - - - I want to bypass censorship. This option recommended in most cases. - - - - - Most VPN protocols are blocked. Recommended if other options are not working. - + Большинство VPN протоколов заблокированы. Рекомендуется, если другие варианты не работают. High diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index 2e772823..85af4694 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -73,23 +73,26 @@ - Add server + Add new connection - - Select data type + + Configure your server + + + + + Open config file, key or QR code - Server IP, login and password - 服务器IP,用户名和密码 + 服务器IP,用户名和密码 - QR code, key or configuration file - 二维码,授权码或者配置文件 + 二维码,授权码或者配置文件 @@ -775,49 +778,49 @@ Already installed containers were found on the server. All installed containers 密码 - + Mount folder on device 挂载文件夹 - + In order to mount remote SFTP folder as local drive, perform following steps: <br> 为将远程 SFTP 文件夹挂载到本地,请执行以下步骤: <br> - - + + <br>1. Install the latest version of <br>1. 安装最新版的 - - + + <br>2. Install the latest version of <br>2. 安装最新版的 - + Detailed instructions 详细说明 - + Remove SFTP and all data stored there 移除SFTP和其本地所有数据 - + Remove SFTP and all data stored there? 移除SFTP和其本地所有数据? - + Continue 继续 - + Cancel 取消 @@ -845,18 +848,18 @@ Already installed containers were found on the server. All installed containers 已拷贝 - + Use <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor Browser</a> to open this url. 用 <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor 浏览器</a> 打开上面网址 - + After installation it takes several minutes while your onion site will become available in the Tor Network. 完成安装几分钟后,洋葱站点才会在 Tor 网络中生效。 - - When configuring WordPress set the this address as domain. + + When configuring WordPress set the this onion address as domain. @@ -864,22 +867,22 @@ Already installed containers were found on the server. All installed containers 配置 WordPress 时,将域设置为此洋葱地址。 - + Remove website 移除网站 - + The site with all data will be removed from the tor network. 网站及其所有数据将从 Tor 网络中删除 - + Continue 继续 - + Cancel 取消 @@ -1141,37 +1144,42 @@ And if you don't like the app, all the more support it - the donation will - + Backup files (*.backup) - + + Backup file saved + + + + Restore from backup 从备份还原 - + Open backup file 打开备份文件 - + Import settings from a backup file? 从备份文件导入设置? - + All current settings will be reset 当前所有设置将重置 - + Continue 继续 - + Cancel 取消 @@ -1228,7 +1236,7 @@ And if you don't like the app, all the more support it - the donation will 配置想要通过VPN访问网站 - + App-based split tunneling 基于应用的隧道分离 @@ -1245,7 +1253,7 @@ And if you don't like the app, all the more support it - the donation will 应用级VPN分流 - + Allows you to use the VPN only for certain applications 仅指定应用使用VPN @@ -1336,32 +1344,37 @@ And if you don't like the app, all the more support it - the donation will - + + Logs file saved + + + + Save logs to file 保存日志到文件 - + Clear logs? 清理日志? - + Continue 继续 - + Cancel 取消 - + Logs have been cleaned up 日志已清理 - + Clear logs 清理日志 @@ -1682,9 +1695,13 @@ It's okay as long as it's from someone you trust. PageSetupWizardCredentials - Server connection - 连接服务器 + 连接服务器 + + + + Configure your server + @@ -1712,22 +1729,28 @@ It's okay as long as it's from someone you trust. 继续 - + + All data you enter will remain strictly confidential +and will not be shared or disclosed to the Amnezia or any third parties + + + + Ip address cannot be empty IP不能为空 - + Enter the address in the format 255.255.255.255:88 按照这种格式输入 255.255.255.255:88 - + Login cannot be empty 账号不能为空 - + Password/private key cannot be empty 密码或私钥不能为空 @@ -2727,16 +2750,6 @@ IKEv2 with IPSec encryption layer. Transmits data over fixed UDP ports 500 and 4 error 0x%1: %2 错误 0x%1: %2 - - - WireGuard Configuration Highlighter - - - - - &Randomize colors - - SelectLanguageDrawer diff --git a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml index ccffe21f..1f7b2f29 100644 --- a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml +++ b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml @@ -27,8 +27,7 @@ DrawerType { Layout.leftMargin: 16 Layout.bottomMargin: 16 - headerText: qsTr("Add server") - descriptionText: qsTr("Select data type") + headerText: qsTr("Add new connection") } LabelWithButtonType { @@ -50,7 +49,7 @@ DrawerType { LabelWithButtonType { Layout.fillWidth: true - text: qsTr("Open QR code, key or config file") + text: qsTr("Open config file, key or QR code") rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { diff --git a/client/ui/qml/Config/GlobalConfig.qml b/client/ui/qml/Config/GlobalConfig.qml index a9edd543..0855101c 100644 --- a/client/ui/qml/Config/GlobalConfig.qml +++ b/client/ui/qml/Config/GlobalConfig.qml @@ -26,4 +26,16 @@ Item { } return false } + + TextEdit{ + id: clipboard + visible: false + } + + function copyToClipBoard(text) { + clipboard.text = text + clipboard.selectAll() + clipboard.copy() + clipboard.select(0, 0) + } } diff --git a/client/ui/qml/Pages2/PageServiceSftpSettings.qml b/client/ui/qml/Pages2/PageServiceSftpSettings.qml index 61ba663d..b12302dd 100644 --- a/client/ui/qml/Pages2/PageServiceSftpSettings.qml +++ b/client/ui/qml/Pages2/PageServiceSftpSettings.qml @@ -96,7 +96,7 @@ PageType { rightImageColor: "#D7D8DB" clickedFunction: function() { - col.copyToClipBoard(descriptionText) + GC.copyToClipBoard(descriptionText) PageController.showNotificationMessage(qsTr("Copied")) } } @@ -113,7 +113,7 @@ PageType { rightImageColor: "#D7D8DB" clickedFunction: function() { - col.copyToClipBoard(descriptionText) + GC.copyToClipBoard(descriptionText) PageController.showNotificationMessage(qsTr("Copied")) } } @@ -130,7 +130,7 @@ PageType { rightImageColor: "#D7D8DB" clickedFunction: function() { - col.copyToClipBoard(descriptionText) + GC.copyToClipBoard(descriptionText) PageController.showNotificationMessage(qsTr("Copied")) } } @@ -147,23 +147,11 @@ PageType { rightImageColor: "#D7D8DB" clickedFunction: function() { - col.copyToClipBoard(descriptionText) + GC.copyToClipBoard(descriptionText) PageController.showNotificationMessage(qsTr("Copied")) } } - TextEdit{ - id: clipboard - visible: false - } - - function copyToClipBoard(text) { - clipboard.text = text - clipboard.selectAll() - clipboard.copy() - clipboard.select(0, 0) - } - BasicButtonType { visible: !GC.isMobile() diff --git a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml index 0d5baa3d..3bfa5bb0 100644 --- a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml +++ b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml @@ -78,23 +78,11 @@ PageType { rightImageColor: "#D7D8DB" clickedFunction: function() { - content.copyToClipBoard(descriptionText) + GC.copyToClipBoard(descriptionText) PageController.showNotificationMessage(qsTr("Copied")) } } - TextEdit{ - id: clipboard - visible: false - } - - function copyToClipBoard(text) { - clipboard.text = text - clipboard.selectAll() - clipboard.copy() - clipboard.select(0, 0) - } - ParagraphTextType { Layout.fillWidth: true Layout.topMargin: 40 @@ -121,7 +109,7 @@ PageType { Layout.leftMargin: 16 Layout.rightMargin: 16 - text: qsTr("When configuring WordPress set the this address as domain.") + text: qsTr("When configuring WordPress set the this onion address as domain.") } BasicButtonType { diff --git a/client/ui/qml/Pages2/PageSettings.qml b/client/ui/qml/Pages2/PageSettings.qml index e0ae7195..92575dda 100644 --- a/client/ui/qml/Pages2/PageSettings.qml +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -110,7 +110,7 @@ PageType { DividerType {} LabelWithButtonType { - visible: GC.isMobile() + visible: GC.isDesktop() Layout.fillWidth: true Layout.preferredHeight: about.height @@ -124,7 +124,7 @@ PageType { } DividerType { - visible: GC.isMobile() + visible: GC.isDesktop() } } } diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml index 6890c7c7..7f0262f9 100644 --- a/client/ui/qml/Pages2/PageSettingsConnection.qml +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -115,7 +115,6 @@ PageType { visible: !GC.isMobile() Layout.fillWidth: true - visible: false text: qsTr("App-based split tunneling") descriptionText: qsTr("Allows you to use the VPN only for certain applications") From c7cd8e4c8042eaa6005fedb47459f263e6efff7b Mon Sep 17 00:00:00 2001 From: pokamest Date: Sun, 15 Oct 2023 02:30:42 +0100 Subject: [PATCH 224/278] Minor ui fixes and refactoring --- client/containers/containers_defs.cpp | 19 +---- client/containers/containers_defs.h | 1 - client/core/scripts_registry.cpp | 1 - client/protocols/protocols_defs.cpp | 14 ++-- client/protocols/protocols_defs.h | 1 - client/translations/amneziavpn_ru.ts | 80 ++++++++++--------- client/translations/amneziavpn_zh_CN.ts | 78 ++++++++++-------- client/ui/controllers/installController.cpp | 4 +- client/ui/models/protocols_model.cpp | 9 +-- .../qml/Pages2/PageSetupWizardInstalling.qml | 4 +- 10 files changed, 103 insertions(+), 108 deletions(-) diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index 1c79874c..d4f45ada 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -89,7 +89,6 @@ QMap ContainerProps::containerHumanNames() { DockerContainer::TorWebSite, QObject::tr("Website in Tor network") }, { DockerContainer::Dns, QObject::tr("Amnezia DNS") }, - //{DockerContainer::FileShare, QObject::tr("SMB file sharing service")}, { DockerContainer::Sftp, QObject::tr("Sftp file sharing service") } }; } @@ -119,7 +118,6 @@ QMap ContainerProps::containerDescriptions() { DockerContainer::TorWebSite, QObject::tr("Deploy a WordPress site on the Tor network in two clicks.") }, { DockerContainer::Dns, QObject::tr("Replace the current DNS server with your own. This will increase your privacy level.") }, - //{DockerContainer::FileShare, QObject::tr("SMB file sharing service - is Window file sharing protocol")}, { DockerContainer::Sftp, QObject::tr("Creates a file vault on your server to securely store and transfer files.") } }; } @@ -190,27 +188,13 @@ QMap ContainerProps::containerDetailedDescriptions() { DockerContainer::TorWebSite, QObject::tr("Website in Tor network") }, { DockerContainer::Dns, QObject::tr("DNS Service") }, - //{DockerContainer::FileShare, QObject::tr("SMB file sharing service - is Window file sharing protocol")}, { DockerContainer::Sftp, QObject::tr("Sftp file sharing service - is secure FTP service") } }; } amnezia::ServiceType ContainerProps::containerService(DockerContainer c) { - switch (c) { - case DockerContainer::None: return ServiceType::None; - case DockerContainer::OpenVpn: return ServiceType::Vpn; - case DockerContainer::Cloak: return ServiceType::Vpn; - case DockerContainer::ShadowSocks: return ServiceType::Vpn; - case DockerContainer::WireGuard: return ServiceType::Vpn; - case DockerContainer::Awg: return ServiceType::Vpn; - case DockerContainer::Ipsec: return ServiceType::Vpn; - case DockerContainer::TorWebSite: return ServiceType::Other; - case DockerContainer::Dns: return ServiceType::Other; - // case DockerContainer::FileShare : return ServiceType::Other; - case DockerContainer::Sftp: return ServiceType::Other; - default: return ServiceType::Other; - } + return ProtocolProps::protocolService(defaultProtocol(c)); } Proto ContainerProps::defaultProtocol(DockerContainer c) @@ -226,7 +210,6 @@ Proto ContainerProps::defaultProtocol(DockerContainer c) case DockerContainer::TorWebSite: return Proto::TorWebSite; case DockerContainer::Dns: return Proto::Dns; - // case DockerContainer::FileShare : return Protocol::FileShare; case DockerContainer::Sftp: return Proto::Sftp; default: return Proto::Any; } diff --git a/client/containers/containers_defs.h b/client/containers/containers_defs.h index ce8a2683..b9cb760d 100644 --- a/client/containers/containers_defs.h +++ b/client/containers/containers_defs.h @@ -26,7 +26,6 @@ namespace amnezia // non-vpn TorWebSite, Dns, - // FileShare, Sftp }; Q_ENUM_NS(DockerContainer) diff --git a/client/core/scripts_registry.cpp b/client/core/scripts_registry.cpp index f209a2b1..61ae8962 100644 --- a/client/core/scripts_registry.cpp +++ b/client/core/scripts_registry.cpp @@ -16,7 +16,6 @@ QString amnezia::scriptFolder(amnezia::DockerContainer container) case DockerContainer::TorWebSite: return QLatin1String("website_tor"); case DockerContainer::Dns: return QLatin1String("dns"); - // case DockerContainer::FileShare: return QLatin1String("file_share"); case DockerContainer::Sftp: return QLatin1String("sftp"); default: return ""; } diff --git a/client/protocols/protocols_defs.cpp b/client/protocols/protocols_defs.cpp index 5964bd87..b3823a11 100644 --- a/client/protocols/protocols_defs.cpp +++ b/client/protocols/protocols_defs.cpp @@ -72,7 +72,6 @@ QMap ProtocolProps::protocolHumanNames() { Proto::TorWebSite, "Website in Tor network" }, { Proto::Dns, "DNS Service" }, - { Proto::FileShare, "File Sharing Service" }, { Proto::Sftp, QObject::tr("Sftp service") } }; } @@ -90,9 +89,11 @@ amnezia::ServiceType ProtocolProps::protocolService(Proto p) case Proto::ShadowSocks: return ServiceType::Vpn; case Proto::WireGuard: return ServiceType::Vpn; case Proto::Awg: return ServiceType::Vpn; + case Proto::Ikev2: return ServiceType::Vpn; + case Proto::TorWebSite: return ServiceType::Other; case Proto::Dns: return ServiceType::Other; - case Proto::FileShare: return ServiceType::Other; + case Proto::Sftp: return ServiceType::Other; default: return ServiceType::Other; } } @@ -111,7 +112,6 @@ int ProtocolProps::defaultPort(Proto p) case Proto::TorWebSite: return -1; case Proto::Dns: return 53; - case Proto::FileShare: return 139; case Proto::Sftp: return 222; default: return -1; } @@ -129,10 +129,10 @@ bool ProtocolProps::defaultPortChangeable(Proto p) case Proto::Ikev2: return false; case Proto::L2tp: return false; - case Proto::TorWebSite: return true; + case Proto::TorWebSite: return false; case Proto::Dns: return false; - case Proto::FileShare: return false; - default: return -1; + case Proto::Sftp: return true; + default: return false; } } @@ -150,7 +150,6 @@ TransportProto ProtocolProps::defaultTransportProto(Proto p) // non-vpn case Proto::TorWebSite: return TransportProto::Tcp; case Proto::Dns: return TransportProto::Udp; - case Proto::FileShare: return TransportProto::Udp; case Proto::Sftp: return TransportProto::Tcp; } } @@ -169,7 +168,6 @@ bool ProtocolProps::defaultTransportProtoChangeable(Proto p) // non-vpn case Proto::TorWebSite: return false; case Proto::Dns: return false; - case Proto::FileShare: return false; case Proto::Sftp: return false; default: return false; } diff --git a/client/protocols/protocols_defs.h b/client/protocols/protocols_defs.h index d6af132b..ed2ed313 100644 --- a/client/protocols/protocols_defs.h +++ b/client/protocols/protocols_defs.h @@ -195,7 +195,6 @@ namespace amnezia // non-vpn TorWebSite, Dns, - FileShare, Sftp }; Q_ENUM_NS(Proto) diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index a6c0464d..d3eef897 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -165,32 +165,32 @@ Already installed containers were found on the server. All installed containers На сервере обнаружены установленные протоколы и сервисы, все они добавлены в приложение - + Settings updated successfully Настройки успешно обновлены - + Server '%1' was removed Сервер '%1' был удален - + All containers from server '%1' have been removed Все протоклы и сервисы были удалены с сервера '%1' - + %1 has been removed from the server '%2' %1 был удален с сервера '%2' - + Please login as the user Пожалуйста, войдите в систему от имени пользователя - + Server added successfully Сервер успешно добавлен @@ -1690,14 +1690,22 @@ and will not be shared or disclosed to the Amnezia or any third parties Сервер уже был добавлен в приложение - Amnesia has detected that your server is currently - Amnesia обнаружила, что ваш сервер в настоящее время + Amnesia обнаружила, что ваш сервер в настоящее время + + + busy installing other software. Amnesia installation + занят установкой других протоколов или сервисов. Установка Amnesia + + + + Amnezia has detected that your server is currently + - busy installing other software. Amnesia installation - занят установкой других протоколов или сервисов. Установка Amnesia + busy installing other software. Amnezia installation + @@ -2406,7 +2414,7 @@ and will not be shared or disclosed to the Amnezia or any third parties IPsec - + The time-tested most popular VPN protocol. Uses a proprietary security protocol with SSL/TLS for encryption and key exchange and supports various authentication methods, making it suitable for a variety of devices and operating systems. @@ -2418,7 +2426,7 @@ Uses a proprietary security protocol with SSL/TLS for encryption and key exchang - + Based on the SOCKS5 proxy protocol, which protects the connection using the AEAD cipher - roughly along the same lines as SSH tunnelling. A Shadowsocks connection is difficult to identify because it is virtually identical to a normal HTTPS connection. However, some traffic analysis systems can still recognise a ShadowSocks connection, so in countries with high levels of censorship we recommend using OpenVPN in conjunction with Cloak. @@ -2430,7 +2438,7 @@ However, some traffic analysis systems can still recognise a ShadowSocks connect - + This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for blocking protection. OpenVPN provides a secure VPN connection by encrypting all Internet traffic between the client and the server. @@ -2450,7 +2458,7 @@ If there is a high level of Internet censorship in your region, we advise you to - + A relatively new popular VPN protocol with a simplified architecture. Provides stable VPN connection, high performance on all devices. Uses hard-coded encryption settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput. * Low power consumption on mobile devices. @@ -2461,7 +2469,7 @@ Provides stable VPN connection, high performance on all devices. Uses hard-coded - + A modern stable protocol. IKEv2 with IPSec encryption layer. Transmits data over fixed UDP ports 500 and 4500 protecting them with strong 3DES and AES crypto algorithms. Allows very fast switching between networks and devices. Due to its security, stability and speed, IKEv2 is currently one of the best VPN solutions for mobile devices. Vulnerable to detection and blocking. @@ -2473,18 +2481,18 @@ IKEv2 with IPSec encryption layer. Transmits data over fixed UDP ports 500 and 4 - + DNS Service DNS Сервис - + Sftp file sharing service Сервис обмена файлами Sftp - + Website in Tor network Веб-сайт в сети Tor @@ -2494,62 +2502,62 @@ IKEv2 with IPSec encryption layer. Transmits data over fixed UDP ports 500 and 4 Amnezia DNS - + OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its own security protocol with SSL/TLS for key exchange. OpenVPN - популярный VPN-протокол, с гибкой настройкой. Имеет собственный протокол безопасности с SSL/TLS для обмена ключами. - + ShadowSocks - masks VPN traffic, making it similar to normal web traffic, but is recognised by analysis systems in some highly censored regions. ShadowSocks - маскирует VPN-трафик под обычный веб-трафик, но распознается системами анализа в некоторых регионах с высоким уровнем цензуры. - + OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against active-probbing detection. Ideal for bypassing blocking in regions with the highest levels of censorship. OpenVPN over Cloak - OpenVPN с маскировкой VPN под web-трафик и защитой от обнаружения active-probbing. Подходит для регионов с самым высоким уровнем цензуры. - + WireGuard - New popular VPN protocol with high performance, high speed and low power consumption. Recommended for regions with low levels of censorship. WireGuard - Популярный VPN-протокол с высокой производительностью, высокой скоростью и низким энергопотреблением. Для регионов с низким уровнем цензуры. - + AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, but very resistant to blockages. Recommended for regions with high levels of censorship. - + IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS. IKEv2 Современный стабильный протокол, немного быстрее других восстанавливает соединение после потери сигнала. Имеет нативную поддержку последних версиий Android и iOS. - + Deploy a WordPress site on the Tor network in two clicks. Разверните сайт на WordPress в сети Tor в два клика. - + Replace the current DNS server with your own. This will increase your privacy level. Замените адрес DNS-сервера на собственный. Это повысит уровень конфиденциальности. - + Creates a file vault on your server to securely store and transfer files. Создайте на сервере файловое хранилище для безопасного хранения и передачи файлов. - + AmneziaWG container AmneziaWG протокол - + Sftp file sharing service - is secure FTP service Сервис обмена файлами Sftp - безопасный FTP-сервис - + Sftp service Сервис SFTP @@ -2831,32 +2839,32 @@ IKEv2 with IPSec encryption layer. Transmits data over fixed UDP ports 500 and 4 amnezia::ContainerProps - + Low Низкий - + Medium or High Спедний или Высокий - + Extreme Экстремальный - + I just want to increase the level of my privacy. Я просто хочу повысить уровень своей приватности. - + I want to bypass censorship. This option recommended in most cases. Я хочу обойти блокировки. Этот вариант рекомендуется в большинстве случаев. - + Most VPN protocols are blocked. Recommended if other options are not working. Большинство VPN протоколов заблокированы. Рекомендуется, если другие варианты не работают. diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index 85af4694..be519a91 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -183,22 +183,22 @@ Already installed containers were found on the server. All installed containers 在服务上发现已经安装协议并添加至应用 - + Settings updated successfully 配置更新成功 - + Server '%1' was removed 已移除服务器 '%1' - + All containers from server '%1' have been removed 服务器 '%1' 的所有容器已移除 - + %1 has been removed from the server '%2' %1 已从服务器 '%2' 上移除 @@ -219,12 +219,12 @@ Already installed containers were found on the server. All installed containers 协议已从 - + Please login as the user 请以用户身份登录 - + Server added successfully 增加服务器成功 @@ -1798,13 +1798,21 @@ and will not be shared or disclosed to the Amnezia or any third parties - Amnesia has detected that your server is currently - Amnezia 检测到您的服务器当前 + Amnezia has detected that your server is currently + + busy installing other software. Amnezia installation + + + + Amnesia has detected that your server is currently + Amnezia 检测到您的服务器当前 + + busy installing other software. Amnesia installation - 正安装其他软件。Amnezia安装 + 正安装其他软件。Amnezia安装 @@ -2313,7 +2321,7 @@ and will not be shared or disclosed to the Amnezia or any third parties QObject - + Sftp service Sftp 服务 @@ -2529,7 +2537,7 @@ and will not be shared or disclosed to the Amnezia or any third parties - + Website in Tor network 在 Tor 网络中架设网站 @@ -2539,57 +2547,57 @@ and will not be shared or disclosed to the Amnezia or any third parties - + Sftp file sharing service SFTP文件共享服务 - + OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its own security protocol with SSL/TLS for key exchange. OpenVPN 是最流行的 VPN 协议,具有灵活的配置选项。它使用自己的安全协议与 SSL/TLS 进行密钥交换。 - + ShadowSocks - masks VPN traffic, making it similar to normal web traffic, but is recognised by analysis systems in some highly censored regions. ShadowSocks - 混淆 VPN 流量,使其与正常的 Web 流量相似,但在一些审查力度高的地区可以被分析系统识别。 - + OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against active-probbing detection. Ideal for bypassing blocking in regions with the highest levels of censorship. OpenVPN over Cloak - OpenVPN 与 VPN 具有伪装成网络流量和防止主动探测检测的保护。非常适合绕过审查力度特别强的地区的封锁。 - + WireGuard - New popular VPN protocol with high performance, high speed and low power consumption. Recommended for regions with low levels of censorship. WireGuard - 新型流行的VPN协议,具有高性能、高速度和低功耗。建议用于审查力度较低的地区 - + AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, but very resistant to blockages. Recommended for regions with high levels of censorship. - + IKEv2 - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS. IKEv2 - 现代稳定协议,相比其他协议较快一些,在信号丢失后恢复连接。Android 和 iOS最新版原生支持。 - + Deploy a WordPress site on the Tor network in two clicks. 只需点击两次即可架设 WordPress 网站到 Tor 网络 - + Replace the current DNS server with your own. This will increase your privacy level. 将当前的 DNS 服务器替换为您自己的。这将提高您的隐私保护级别。 - + Creates a file vault on your server to securely store and transfer files. 在您的服务器上创建文件仓库,以便安全地存储和传输文件 - + The time-tested most popular VPN protocol. Uses a proprietary security protocol with SSL/TLS for encryption and key exchange and supports various authentication methods, making it suitable for a variety of devices and operating systems. @@ -2601,7 +2609,7 @@ Uses a proprietary security protocol with SSL/TLS for encryption and key exchang - + Based on the SOCKS5 proxy protocol, which protects the connection using the AEAD cipher - roughly along the same lines as SSH tunnelling. A Shadowsocks connection is difficult to identify because it is virtually identical to a normal HTTPS connection. However, some traffic analysis systems can still recognise a ShadowSocks connection, so in countries with high levels of censorship we recommend using OpenVPN in conjunction with Cloak. @@ -2613,7 +2621,7 @@ However, some traffic analysis systems can still recognise a ShadowSocks connect - + This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for blocking protection. OpenVPN provides a secure VPN connection by encrypting all Internet traffic between the client and the server. @@ -2633,7 +2641,7 @@ If there is a high level of Internet censorship in your region, we advise you to - + A relatively new popular VPN protocol with a simplified architecture. Provides stable VPN connection, high performance on all devices. Uses hard-coded encryption settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput. * Low power consumption on mobile devices. @@ -2644,7 +2652,7 @@ Provides stable VPN connection, high performance on all devices. Uses hard-coded - + A modern stable protocol. IKEv2 with IPSec encryption layer. Transmits data over fixed UDP ports 500 and 4500 protecting them with strong 3DES and AES crypto algorithms. Allows very fast switching between networks and devices. Due to its security, stability and speed, IKEv2 is currently one of the best VPN solutions for mobile devices. Vulnerable to detection and blocking. @@ -2672,7 +2680,7 @@ IKEv2 with IPSec encryption layer. Transmits data over fixed UDP ports 500 and 4 WireGuard 容器 - + AmneziaWG container @@ -2681,12 +2689,12 @@ IKEv2 with IPSec encryption layer. Transmits data over fixed UDP ports 500 and 4 IPsec 容器 - + DNS Service DNS 服务 - + Sftp file sharing service - is secure FTP service Sftp 文件共享服务 - 安全的 FTP 服务 @@ -2972,32 +2980,32 @@ IKEv2 with IPSec encryption layer. Transmits data over fixed UDP ports 500 and 4 amnezia::ContainerProps - + Low - + Medium or High - + Extreme - + I just want to increase the level of my privacy. - + I want to bypass censorship. This option recommended in most cases. - + Most VPN protocols are blocked. Recommended if other options are not working. diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 34917cac..6eec9c6b 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -183,7 +183,9 @@ void InstallController::installContainer(DockerContainer container, QJsonObject "All installed containers have been added to the application"); } - m_containersModel->setData(m_containersModel->index(0, 0), container, ContainersModel::Roles::IsDefaultRole); + if (ContainerProps::containerService(container) == ServiceType::Vpn) { + m_containersModel->setData(m_containersModel->index(0, 0), container, ContainersModel::Roles::IsDefaultRole); + } emit installContainerFinished(finishMessage, ContainerProps::containerService(container) == ServiceType::Other); return; } diff --git a/client/ui/models/protocols_model.cpp b/client/ui/models/protocols_model.cpp index 8c999470..5826025e 100644 --- a/client/ui/models/protocols_model.cpp +++ b/client/ui/models/protocols_model.cpp @@ -78,12 +78,11 @@ PageLoader::PageEnum ProtocolsModel::protocolPage(Proto protocol) const case Proto::ShadowSocks: return PageLoader::PageEnum::PageProtocolShadowSocksSettings; case Proto::WireGuard: return PageLoader::PageEnum::PageProtocolWireGuardSettings; case Proto::Ikev2: return PageLoader::PageEnum::PageProtocolIKev2Settings; - case Proto::L2tp: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; + case Proto::L2tp: return PageLoader::PageEnum::PageProtocolIKev2Settings; // non-vpn - case Proto::TorWebSite: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; - case Proto::Dns: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; - case Proto::FileShare: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; - case Proto::Sftp: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; + case Proto::TorWebSite: return PageLoader::PageEnum::PageServiceTorWebsiteSettings; + case Proto::Dns: return PageLoader::PageEnum::PageServiceDnsSettings; + case Proto::Sftp: return PageLoader::PageEnum::PageServiceSftpSettings; default: return PageLoader::PageEnum::PageProtocolOpenVpnSettings; } } diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index 84f6be89..bd1fd7e6 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -59,8 +59,8 @@ PageType { function onServerIsBusy(isBusy) { if (isBusy) { - root.progressBarText = qsTr("Amnesia has detected that your server is currently ") + - qsTr("busy installing other software. Amnesia installation ") + + root.progressBarText = qsTr("Amnezia has detected that your server is currently ") + + qsTr("busy installing other software. Amnezia installation ") + qsTr("will pause until the server finishes installing other software") root.isTimerRunning = false } else { From 8c1835950bf64ebdf188769359bc908d501ba6d1 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Sun, 15 Oct 2023 15:17:04 +0800 Subject: [PATCH 225/278] added transparent-background, for blocking clicked event --- .../ConnectionTypeSelectionDrawer.qml | 5 +- client/ui/qml/Components/QuestionDrawer.qml | 7 +- .../qml/Components/SelectLanguageDrawer.qml | 6 +- .../qml/Components/ShareConnectionDrawer.qml | 16 ++++- client/ui/qml/Controls2/Drawer2Type.qml | 64 ++++++++++++++----- client/ui/qml/Controls2/DropDownType.qml | 17 +++-- .../qml/Pages2/PageProtocolCloakSettings.qml | 2 + .../Pages2/PageProtocolOpenVpnSettings.qml | 4 ++ client/ui/qml/Pages2/PageProtocolRaw.qml | 5 +- .../PageProtocolShadowSocksSettings.qml | 2 + .../ui/qml/Pages2/PageSettingsServerData.qml | 8 ++- .../ui/qml/Pages2/PageSettingsServerInfo.qml | 5 +- .../qml/Pages2/PageSettingsSplitTunneling.qml | 14 +++- .../PageSetupWizardProtocolSettings.qml | 7 +- client/ui/qml/Pages2/PageShare.qml | 6 ++ 15 files changed, 134 insertions(+), 34 deletions(-) diff --git a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml index a368d45c..ac04e444 100644 --- a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml +++ b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml @@ -12,9 +12,12 @@ Drawer2Type { id: root width: parent.width - height: parent.height * 0.4375 + height: parent.height + contentHeight: parent.height * 0.4375 ColumnLayout { + parent: root.contentParent + anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right diff --git a/client/ui/qml/Components/QuestionDrawer.qml b/client/ui/qml/Components/QuestionDrawer.qml index 06f4ae24..edc8df9e 100644 --- a/client/ui/qml/Components/QuestionDrawer.qml +++ b/client/ui/qml/Components/QuestionDrawer.qml @@ -17,9 +17,12 @@ Drawer2Type { property var noButtonFunction width: parent.width - height: parent.height * 0.5 + height: parent.height + contentHeight: parent.height * 0.5 ColumnLayout { + parent: root.contentParent + anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right @@ -27,6 +30,8 @@ Drawer2Type { anchors.rightMargin: 16 anchors.leftMargin: 16 + // visible: false + spacing: 8 Header2TextType { diff --git a/client/ui/qml/Components/SelectLanguageDrawer.qml b/client/ui/qml/Components/SelectLanguageDrawer.qml index f27fce6c..e6fdc2b5 100644 --- a/client/ui/qml/Components/SelectLanguageDrawer.qml +++ b/client/ui/qml/Components/SelectLanguageDrawer.qml @@ -9,11 +9,14 @@ Drawer2Type { id: root width: parent.width - height: parent.height * 0.9 + height: parent.height + contentHeight: parent.height * 0.9 ColumnLayout { id: backButton + parent: root.contentParent + anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right @@ -28,6 +31,7 @@ Drawer2Type { } FlickableType { + parent: root.contentParent anchors.top: backButton.bottom anchors.left: parent.left anchors.right: parent.right diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index cb74f42e..22c567a8 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -28,7 +28,8 @@ Drawer2Type { property string configFileName: "amnezia_config.vpn" width: parent.width - height: parent.height * 0.9 + height: parent.height + contentHeight: parent.height * 0.9 onClose: { configExtension = ".vpn" @@ -41,6 +42,9 @@ Drawer2Type { Header2Type { id: header + + parent: root.contentParent + anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right @@ -50,6 +54,8 @@ Drawer2Type { } FlickableType { + parent: root.contentParent + anchors.top: header.bottom anchors.bottom: parent.bottom contentHeight: content.height + 32 @@ -135,11 +141,15 @@ Drawer2Type { parent: root width: parent.width - height: parent.height * 0.9 + height: parent.height + + contentHeight: parent.height * 0.9 BackButtonType { id: backButton + parent: configContentDrawer.contentParent + anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right @@ -151,6 +161,8 @@ Drawer2Type { } FlickableType { + parent: configContentDrawer.contentParent + anchors.top: backButton.bottom anchors.left: parent.left anchors.right: parent.right diff --git a/client/ui/qml/Controls2/Drawer2Type.qml b/client/ui/qml/Controls2/Drawer2Type.qml index eae28846..3f877900 100644 --- a/client/ui/qml/Controls2/Drawer2Type.qml +++ b/client/ui/qml/Controls2/Drawer2Type.qml @@ -16,8 +16,6 @@ Item { visible: false - parent: Overlay.overlay - signal close() property bool needCloseButton: true @@ -27,7 +25,10 @@ Item { property int collapsedHeight: 0 property bool needCollapsed: false - + property double scaely + property int contentHeight: 0 + property Item contentParent: contentArea + // property bool inContentArea: false y: parent.height - root.height @@ -37,23 +38,49 @@ Item { id: draw2Background anchors { left: root.left; right: root.right; top: root.top } - height: root.height + height: root.parent.height + width: parent.width radius: 16 - color: root.defaultColor - border.color: root.borderColor + color: "transparent" + border.color: "transparent" //"green" border.width: 1 + visible: true Rectangle { - width: parent.radius - height: parent.radius - anchors.bottom: parent.bottom + id: semiArea + anchors.top: parent.top + height: parent.height - contentHeight anchors.right: parent.right anchors.left: parent.left - color: parent.color + visible: true + color: "transparent" + } + + Rectangle { + id: contentArea + anchors.top: semiArea.bottom + height: contentHeight + radius: 16 + color: root.defaultColor + border.width: 1 + border.color: root.borderColor + width: parent.width + visible: true + + Rectangle { + width: parent.radius + height: parent.radius + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.left: parent.left + color: parent.color + } } } + MouseArea { + id: fullArea anchors.fill: parent enabled: (root.state === "expanded" || root.state === "opened") onClicked: { @@ -75,6 +102,7 @@ Item { id: dragArea anchors.fill: parent + cursorShape: (root.state === "opened" || root.state === "expanded") ? Qt.PointingHandCursor : Qt.ArrowCursor hoverEnabled: true @@ -108,12 +136,14 @@ Item { } onClicked: { - if (root.state === "opened") { - root.state = "closed" + if (root.state === "expanded") { + root.state = "collapsed" + return } - if (root.state == "expanded") { - root.state == "collapsed" + if (root.state === "opened") { + root.state = "closed" + return } } } @@ -217,7 +247,7 @@ Item { target: root property: "y" from: parent.height - to: parent.height - root.height + to: 0 duration: 200 } @@ -226,9 +256,11 @@ Item { return } + root.y = 0 root.state = "opened" root.visible = true - root.y = parent.height - root.height + root.height = parent.height + contentArea.height = contentHeight dragArea.drag.maximumY = parent.height dragArea.drag.minimumY = parent.height - root.height diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index cab2ef4e..83518bef 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -40,6 +40,8 @@ Item { property alias menuVisible: menu.visible + property Item drawerParent: root + implicitWidth: rootButtonContent.implicitWidth implicitHeight: rootButtonContent.implicitHeight @@ -161,18 +163,21 @@ Item { } } - // Drawer2Type { - DrawerType { + //DrawerType { + Drawer2Type { id: menu - width: parent.width - height: parent.height * drawerHeight + parent: drawerParent - // parent: root.parent.parent + width: parent.width + height: parent.height + contentHeight: parent.height * drawerHeight ColumnLayout { id: header + parent: menu.contentParent + anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right @@ -187,6 +192,8 @@ Item { } FlickableType { + parent: menu.contentParent + anchors.top: header.bottom anchors.topMargin: 16 contentHeight: col.implicitHeight diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml index 78e666a7..b0f36971 100644 --- a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -117,6 +117,8 @@ PageType { Layout.fillWidth: true Layout.topMargin: 16 + drawerParent: root + descriptionText: qsTr("Cipher") headerText: qsTr("Cipher") diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index f03cfc07..2861ece3 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -156,6 +156,8 @@ PageType { Layout.fillWidth: true Layout.topMargin: 20 + drawerParent: root + enabled: !autoNegotiateEncryprionSwitcher.checked descriptionText: qsTr("Hash") @@ -202,6 +204,8 @@ PageType { Layout.fillWidth: true Layout.topMargin: 16 + drawerParent: root + enabled: !autoNegotiateEncryprionSwitcher.checked descriptionText: qsTr("Cipher") diff --git a/client/ui/qml/Pages2/PageProtocolRaw.qml b/client/ui/qml/Pages2/PageProtocolRaw.qml index 3e0fe5ab..f2a17686 100644 --- a/client/ui/qml/Pages2/PageProtocolRaw.qml +++ b/client/ui/qml/Pages2/PageProtocolRaw.qml @@ -95,11 +95,14 @@ PageType { parent: root width: parent.width - height: parent.height * 0.9 + height: parent.height + contentHeight: parent.height * 0.9 BackButtonType { id: backButton + parent: configContentDrawer.contentParent + anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right diff --git a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml index 2453281f..75853d1f 100644 --- a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml @@ -95,6 +95,8 @@ PageType { Layout.fillWidth: true Layout.topMargin: 20 + drawerParent: root + descriptionText: qsTr("Cipher") headerText: qsTr("Cipher") diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index c90e66b3..5e3f1939 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -141,7 +141,7 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.visible = false + questionDrawer.close() PageController.showBusyIndicator(true) if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { ConnectionController.closeConnection() @@ -150,9 +150,9 @@ PageType { PageController.showBusyIndicator(false) } questionDrawer.noButtonFunction = function() { - questionDrawer.visible = false + questionDrawer.close() } - questionDrawer.visible = true + questionDrawer.open() } } @@ -192,6 +192,8 @@ PageType { QuestionDrawer { id: questionDrawer + + parent: root } } } diff --git a/client/ui/qml/Pages2/PageSettingsServerInfo.qml b/client/ui/qml/Pages2/PageSettingsServerInfo.qml index d534b509..f6d569dd 100644 --- a/client/ui/qml/Pages2/PageSettingsServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsServerInfo.qml @@ -80,7 +80,8 @@ PageType { parent: root width: root.width - height: root.height * 0.35 + height: root.height // * 0.35 + contentHeight: root.height * 0.35 onVisibleChanged: { if (serverNameEditDrawer.visible) { @@ -89,6 +90,8 @@ PageType { } ColumnLayout { + parent: serverNameEditDrawer.contentParent + anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index 406fae66..a88c576b 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -108,6 +108,8 @@ PageType { DropDownType { id: selector + drawerParent: root + Layout.fillWidth: true Layout.topMargin: 32 Layout.leftMargin: 16 @@ -264,11 +266,14 @@ PageType { id: moreActionsDrawer width: parent.width - height: parent.height * 0.4375 + height: parent.height + contentHeight: parent.height * 0.4375 parent: root FlickableType { + parent: moreActionsDrawer.contentParent + anchors.fill: parent contentHeight: moreActionsDrawerContent.height ColumnLayout { @@ -331,13 +336,16 @@ PageType { id: importSitesDrawer width: parent.width - height: parent.height * 0.4375 + height: parent.height + contentHeight: parent.height * 0.4375 parent: root BackButtonType { id: importSitesDrawerBackButton + parent: importSitesDrawer.contentParent + anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right @@ -349,6 +357,8 @@ PageType { } FlickableType { + parent: importSitesDrawer.contentParent + anchors.top: importSitesDrawerBackButton.bottom anchors.left: parent.left anchors.right: parent.right diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 794ec90b..6cdda364 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -101,13 +101,16 @@ PageType { id: showDetailsDrawer width: parent.width - height: parent.height * 0.9 + height: parent.height + contentHeight: parent.height * 0.9 parent: root BackButtonType { id: showDetailsBackButton + parent: showDetailsDrawer.contentParent + anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right @@ -119,6 +122,8 @@ PageType { } FlickableType { + parent: showDetailsDrawer.contentParent + anchors.top: showDetailsBackButton.bottom anchors.left: parent.left anchors.right: parent.right diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 6c641ea6..96399153 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -179,6 +179,8 @@ PageType { DropDownType { id: serverSelector + drawerParent: root + signal severSelectorIndexChanged property int currentIndex: 0 @@ -241,6 +243,8 @@ PageType { DropDownType { id: protocolSelector + drawerParent: root + visible: accessTypeSelector.currentIndex === 0 Layout.fillWidth: true @@ -330,6 +334,8 @@ PageType { DropDownType { id: exportTypeSelector + drawerParent: root + property int currentIndex: 0 Layout.fillWidth: true From a75bd07cd8b604bbe5f6894ac8e861f57efe8df5 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Sun, 15 Oct 2023 15:54:05 +0800 Subject: [PATCH 226/278] fixed the clicked event --- client/ui/qml/Controls2/Drawer2Type.qml | 170 +++++++++++++----------- 1 file changed, 89 insertions(+), 81 deletions(-) diff --git a/client/ui/qml/Controls2/Drawer2Type.qml b/client/ui/qml/Controls2/Drawer2Type.qml index 3f877900..2f10ec5d 100644 --- a/client/ui/qml/Controls2/Drawer2Type.qml +++ b/client/ui/qml/Controls2/Drawer2Type.qml @@ -34,6 +34,8 @@ Item { state: "closed" + Drag.active: dragArea.drag.active + Rectangle { id: draw2Background @@ -46,104 +48,110 @@ Item { border.width: 1 visible: true - Rectangle { - id: semiArea - anchors.top: parent.top - height: parent.height - contentHeight - anchors.right: parent.right - anchors.left: parent.left - visible: true - color: "transparent" - } + MouseArea { + id: fullArea + anchors.fill: parent + enabled: (root.state === "expanded" || root.state === "opened") + onClicked: { + if (root.state === "expanded") { + root.state = "collapsed" + return + } - Rectangle { - id: contentArea - anchors.top: semiArea.bottom - height: contentHeight - radius: 16 - color: root.defaultColor - border.width: 1 - border.color: root.borderColor - width: parent.width - visible: true + if (root.state === "opened") { + root.state = "closed" + return + } + } Rectangle { - width: parent.radius - height: parent.radius - anchors.bottom: parent.bottom + id: semiArea + anchors.top: parent.top + height: parent.height - contentHeight anchors.right: parent.right anchors.left: parent.left - color: parent.color - } - } - } - - - MouseArea { - id: fullArea - anchors.fill: parent - enabled: (root.state === "expanded" || root.state === "opened") - onClicked: { - if (root.state === "expanded") { - root.state = "collapsed" - return + visible: true + color: "transparent" } - if (root.state === "opened") { - root.state = "closed" - return - } - } - } + Rectangle { + id: contentArea - Drag.active: dragArea.drag.active + anchors.top: semiArea.bottom + height: contentHeight + radius: 16 + color: root.defaultColor + border.width: 1 + border.color: root.borderColor + width: parent.width + visible: true - MouseArea { - id: dragArea + Rectangle { + width: parent.radius + height: parent.radius + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.left: parent.left + color: parent.color + } - anchors.fill: parent + MouseArea { + id: dragArea - cursorShape: (root.state === "opened" || root.state === "expanded") ? Qt.PointingHandCursor : Qt.ArrowCursor - hoverEnabled: true + anchors.fill: parent - drag.target: parent - drag.axis: Drag.YAxis - drag.maximumY: parent.height - drag.minimumY: parent.height - root.height + cursorShape: (root.state === "opened" || root.state === "expanded") ? Qt.PointingHandCursor : Qt.ArrowCursor + hoverEnabled: true - /** If drag area is released at any point other than min or max y, transition to the other state */ - onReleased: { - if (root.state === "closed" && root.y < dragArea.drag.maximumY) { - root.state = "opened" - PageController.drawerOpen() - return - } + drag.target: root + drag.axis: Drag.YAxis + drag.maximumY: root.height + drag.minimumY: root.height - root.height - if (root.state === "opened" && root.y > dragArea.drag.minimumY) { - root.state = "closed" - PageController.drawerClose() - return - } + /** If drag area is released at any point other than min or max y, transition to the other state */ + onReleased: { + if (root.state === "closed" && root.y < dragArea.drag.maximumY) { + root.state = "opened" + PageController.drawerOpen() + return + } - if (root.state === "collapsed" && root.y < dragArea.drag.maximumY) { - root.state = "expanded" - return - } - if (root.state === "expanded" && root.y > dragArea.drag.minimumY) { - root.state = "collapsed" - return - } - } + if (root.state === "opened" && root.y > dragArea.drag.minimumY) { + root.state = "closed" + PageController.drawerClose() + return + } - onClicked: { - if (root.state === "expanded") { - root.state = "collapsed" - return - } + if (root.state === "collapsed" && root.y < dragArea.drag.maximumY) { + root.state = "expanded" + return + } + if (root.state === "expanded" && root.y > dragArea.drag.minimumY) { + root.state = "collapsed" + return + } + } - if (root.state === "opened") { - root.state = "closed" - return + onClicked: { + if (root.state === "expanded") { + root.state = "collapsed" + return + } + + if (root.state === "opened") { + root.state = "closed" + return + } + } + +// onEntered: { +// fullArea.enabled = false +// } + +// onExited: { +// fullArea.enabled = true +// } + } } } } From d0f83584313d60c4c0507b60d5d78118ffb9cbdf Mon Sep 17 00:00:00 2001 From: ronoaer Date: Sun, 15 Oct 2023 17:29:22 +0800 Subject: [PATCH 227/278] removed invalid code, and fixed top button hidden-shown --- client/ui/qml/Controls2/Drawer2Type.qml | 67 +++++++++---------- .../Pages2/PageProtocolOpenVpnSettings.qml | 4 +- client/ui/qml/Pages2/PageProtocolRaw.qml | 4 +- .../ui/qml/Pages2/PageServiceDnsSettings.qml | 4 +- .../ui/qml/Pages2/PageServiceSftpSettings.qml | 4 +- .../Pages2/PageServiceTorWebsiteSettings.qml | 4 +- .../ui/qml/Pages2/PageSettingsApplication.qml | 4 +- client/ui/qml/Pages2/PageSettingsBackup.qml | 4 +- client/ui/qml/Pages2/PageSettingsDns.qml | 4 +- client/ui/qml/Pages2/PageSettingsLogging.qml | 4 +- .../ui/qml/Pages2/PageSettingsServerData.qml | 8 +-- .../qml/Pages2/PageSettingsServerProtocol.qml | 4 +- .../qml/Pages2/PageSettingsSplitTunneling.qml | 4 +- client/ui/qml/Pages2/PageStart.qml | 2 +- 14 files changed, 58 insertions(+), 63 deletions(-) diff --git a/client/ui/qml/Controls2/Drawer2Type.qml b/client/ui/qml/Controls2/Drawer2Type.qml index 2f10ec5d..2daae742 100644 --- a/client/ui/qml/Controls2/Drawer2Type.qml +++ b/client/ui/qml/Controls2/Drawer2Type.qml @@ -14,9 +14,9 @@ Item { } } - visible: false + signal closed - signal close() + visible: false property bool needCloseButton: true @@ -28,7 +28,6 @@ Item { property double scaely property int contentHeight: 0 property Item contentParent: contentArea - // property bool inContentArea: false y: parent.height - root.height @@ -44,7 +43,7 @@ Item { width: parent.width radius: 16 color: "transparent" - border.color: "transparent" //"green" + border.color: "transparent" border.width: 1 visible: true @@ -52,6 +51,8 @@ Item { id: fullArea anchors.fill: parent enabled: (root.state === "expanded" || root.state === "opened") + hoverEnabled: true + onClicked: { if (root.state === "expanded") { root.state = "collapsed" @@ -65,7 +66,7 @@ Item { } Rectangle { - id: semiArea + id: placeAreaHolder anchors.top: parent.top height: parent.height - contentHeight anchors.right: parent.right @@ -77,7 +78,7 @@ Item { Rectangle { id: contentArea - anchors.top: semiArea.bottom + anchors.top: placeAreaHolder.bottom height: contentHeight radius: 16 color: root.defaultColor @@ -112,13 +113,13 @@ Item { onReleased: { if (root.state === "closed" && root.y < dragArea.drag.maximumY) { root.state = "opened" - PageController.drawerOpen() + // PageController.drawerOpen() return } if (root.state === "opened" && root.y > dragArea.drag.minimumY) { root.state = "closed" - PageController.drawerClose() + // PageController.drawerClose() return } @@ -134,23 +135,17 @@ Item { onClicked: { if (root.state === "expanded") { + // PageController.drawerOpen() root.state = "collapsed" return } if (root.state === "opened") { + // PageController.drawerClose() root.state = "closed" return } } - -// onEntered: { -// fullArea.enabled = false -// } - -// onExited: { -// fullArea.enabled = true -// } } } } @@ -163,14 +158,23 @@ Item { PageController.updateNavigationBarColor(initialPageNavigationBarColor) } - PageController.drawerClose() + if (needCloseButton) { + PageController.drawerClose() + } + + closed() + return } if (root.state === "expanded" || root.state === "opened") { if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) { PageController.updateNavigationBarColor(0xFF1C1D21) } - PageController.drawerOpen() + + if (needCloseButton) { + PageController.drawerOpen() + } + return } } @@ -275,31 +279,22 @@ Item { animationVisible.running = true - - if (needCloseButton) { - PageController.drawerOpen() - } - - if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) { - PageController.updateNavigationBarColor(0xFF1C1D21) - } } - function onClose() { - if (needCloseButton) { - PageController.drawerClose() - } - - var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor() - if (initialPageNavigationBarColor !== 0xFF1C1D21) { - PageController.updateNavigationBarColor(initialPageNavigationBarColor) - } - + function close() { root.visible = false + root.state = "closed" } onVisibleChanged: { + // e.g cancel, ...... if (!visible) { + if (root.state === "opened") { + if (needCloseButton) { + PageController.drawerClose() + } + } + close() } } diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index 2861ece3..3e75474b 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -369,12 +369,12 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.onClose() + questionDrawer.close() PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeCurrentlyProcessedContainer() } questionDrawer.noButtonFunction = function() { - questionDrawer.onClose() + questionDrawer.close() } questionDrawer.open() } diff --git a/client/ui/qml/Pages2/PageProtocolRaw.qml b/client/ui/qml/Pages2/PageProtocolRaw.qml index f2a17686..23f192c7 100644 --- a/client/ui/qml/Pages2/PageProtocolRaw.qml +++ b/client/ui/qml/Pages2/PageProtocolRaw.qml @@ -183,12 +183,12 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.onClose() + questionDrawer.close() PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeCurrentlyProcessedContainer() } questionDrawer.noButtonFunction = function() { - questionDrawer.onClose() + questionDrawer.close() } questionDrawer.open() } diff --git a/client/ui/qml/Pages2/PageServiceDnsSettings.qml b/client/ui/qml/Pages2/PageServiceDnsSettings.qml index 70aba8e2..9ad3b289 100644 --- a/client/ui/qml/Pages2/PageServiceDnsSettings.qml +++ b/client/ui/qml/Pages2/PageServiceDnsSettings.qml @@ -68,12 +68,12 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.onClose() + questionDrawer.close() PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeCurrentlyProcessedContainer() } questionDrawer.noButtonFunction = function() { - questionDrawer.onClose() + questionDrawer.close() } questionDrawer.open() } diff --git a/client/ui/qml/Pages2/PageServiceSftpSettings.qml b/client/ui/qml/Pages2/PageServiceSftpSettings.qml index 0af47cdd..189267af 100644 --- a/client/ui/qml/Pages2/PageServiceSftpSettings.qml +++ b/client/ui/qml/Pages2/PageServiceSftpSettings.qml @@ -265,12 +265,12 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.onClose() + questionDrawer.close() PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeCurrentlyProcessedContainer() } questionDrawer.noButtonFunction = function() { - questionDrawer.onClose() + questionDrawer.close() } questionDrawer.open() } diff --git a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml index deff35eb..b69f5f63 100644 --- a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml +++ b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml @@ -143,12 +143,12 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.onClose() + questionDrawer.close() PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeCurrentlyProcessedContainer() } questionDrawer.noButtonFunction = function() { - questionDrawer.onClose() + questionDrawer.close() } questionDrawer.open() } diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index 225e2b14..bd602636 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -152,12 +152,12 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.onClose() + questionDrawer.close() SettingsController.clearSettings() PageController.replaceStartPage() } questionDrawer.noButtonFunction = function() { - questionDrawer.onClose() + questionDrawer.close() } questionDrawer.open() } diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml index 61370dab..1fb160a1 100644 --- a/client/ui/qml/Pages2/PageSettingsBackup.qml +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -138,13 +138,13 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.onClose() + questionDrawer.close() PageController.showBusyIndicator(true) SettingsController.restoreAppConfig(filePath) PageController.showBusyIndicator(false) } questionDrawer.noButtonFunction = function() { - questionDrawer.onClose() + questionDrawer.close() } questionDrawer.open() } diff --git a/client/ui/qml/Pages2/PageSettingsDns.qml b/client/ui/qml/Pages2/PageSettingsDns.qml index 917c6992..553eeacd 100644 --- a/client/ui/qml/Pages2/PageSettingsDns.qml +++ b/client/ui/qml/Pages2/PageSettingsDns.qml @@ -91,7 +91,7 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.onClose() + questionDrawer.close() SettingsController.primaryDns = "1.1.1.1" primaryDns.textFieldText = SettingsController.primaryDns SettingsController.secondaryDns = "1.0.0.1" @@ -99,7 +99,7 @@ PageType { PageController.showNotificationMessage(qsTr("Settings have been reset")) } questionDrawer.noButtonFunction = function() { - questionDrawer.onClose() + questionDrawer.close() } questionDrawer.open() } diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml index 1532d1dc..9b9899ab 100644 --- a/client/ui/qml/Pages2/PageSettingsLogging.qml +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -146,14 +146,14 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.onClose() + questionDrawer.close() PageController.showBusyIndicator(true) SettingsController.clearLogs() PageController.showBusyIndicator(false) PageController.showNotificationMessage(qsTr("Logs have been cleaned up")) } questionDrawer.noButtonFunction = function() { - questionDrawer.onClose() + questionDrawer.close() } questionDrawer.open() } diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 5e3f1939..b130eef9 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -94,13 +94,13 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.onClose() + questionDrawer.close() PageController.showBusyIndicator(true) SettingsController.clearCachedProfiles() PageController.showBusyIndicator(false) } questionDrawer.noButtonFunction = function() { - questionDrawer.onClose() + questionDrawer.close() } questionDrawer.open() } @@ -172,7 +172,7 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.onClose() + questionDrawer.close() PageController.goToPage(PageEnum.PageDeinstalling) if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { ConnectionController.closeConnection() @@ -180,7 +180,7 @@ PageType { InstallController.removeAllContainers() } questionDrawer.noButtonFunction = function() { - questionDrawer.onClose() + questionDrawer.close() } questionDrawer.open() } diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml index 594e4520..d1c8b22b 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -119,12 +119,12 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.onClose() + questionDrawer.close() PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeCurrentlyProcessedContainer() } questionDrawer.noButtonFunction = function() { - questionDrawer.onClose() + questionDrawer.close() } questionDrawer.open() } diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index a88c576b..5bbd1003 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -202,11 +202,11 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.onClose() + questionDrawer.close() SitesController.removeSite(index) } questionDrawer.noButtonFunction = function() { - questionDrawer.onClose() + questionDrawer.close() } questionDrawer.open() } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 923d1e79..55795465 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -245,7 +245,7 @@ PageType { ConnectionTypeSelectionDrawer { id: connectionTypeSelection - onClose: function() { + onClosed: { tabBar.setCurrentIndex(tabBar.previousIndex) } } From 29b4966119a57bb4631bb8ea289a5a27444471d6 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Sun, 15 Oct 2023 17:34:35 +0800 Subject: [PATCH 228/278] shown ConnectionTypeSelectionDrawer on top level alway --- client/ui/qml/Pages2/PageStart.qml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 55795465..8afad392 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -245,6 +245,8 @@ PageType { ConnectionTypeSelectionDrawer { id: connectionTypeSelection + z: 1 + onClosed: { tabBar.setCurrentIndex(tabBar.previousIndex) } From 6d05b6845edbe1b32de680c4c5dd57f3d822e13d Mon Sep 17 00:00:00 2001 From: pokamest Date: Sun, 15 Oct 2023 10:53:09 +0100 Subject: [PATCH 229/278] VPN protocol descriptions updated --- client/containers/containers_defs.cpp | 97 ++++++++----- client/translations/amneziavpn_ru.ts | 175 +++++++++++++----------- client/translations/amneziavpn_zh_CN.ts | 95 +++++++------ 3 files changed, 211 insertions(+), 156 deletions(-) diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index d4f45ada..b6f1b111 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -127,24 +127,30 @@ QMap ContainerProps::containerDetailedDescriptions() return { { DockerContainer::OpenVpn, QObject::tr( - "The time-tested most popular VPN protocol.\n\n" - "Uses a proprietary security protocol with SSL/TLS for encryption and key exchange and supports " - "various authentication methods, making it suitable for a variety of devices and operating " - "systems.\n\n" - "* Normal power consumption on mobile devices\n" - "* Flexible customisation to suit user needs to work with different operating systems and devices.\n" - "* Recognised by DPI analysis systems and therefore susceptible to blocking.\n" - "* Can operate over both TCP and UDP network protocols.") }, + "OpenVPN stands as one of the most popular and time-tested VPN protocols available.\n" + "It employs its unique security protocol, " + "leveraging the strength of SSL/TLS for encryption and key exchange. " + "Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, " + "catering to a wide range of devices and operating systems. " + "Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, " + "which continually reinforces its security. " + "With a strong balance of performance, security, and compatibility, " + "OpenVPN remains a top choice for privacy-conscious individuals and businesses alike.\n\n" + "* Available in the AmneziaVPN across all platforms\n" + "* Normal power consumption on mobile devices\n" + "* Flexible customisation to suit user needs to work with different operating systems and devices\n" + "* Recognised by DPI analysis systems and therefore susceptible to blocking\n" + "* Can operate over both TCP and UDP network protocols.") }, { DockerContainer::ShadowSocks, - QObject::tr("Based on the SOCKS5 proxy protocol, which protects the connection using the AEAD cipher - " - "roughly along the same lines as SSH tunnelling. A Shadowsocks connection is difficult to " - "identify because it is virtually identical to a normal HTTPS connection.\n\n" - "However, some traffic analysis systems can still recognise a ShadowSocks connection, so in " - "countries with high levels of censorship we recommend using OpenVPN in conjunction with Cloak.\n" - "* Average power consumption on mobile devices (higher than OpenVPN).\n" - "* It is possible to configure the encryption protocol.\n" - "* Recognised by some DPI analysis systems\n" - "* Works only via TCP network protocol\n") }, + QObject::tr("Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. " + "Although Shadowsocks is designed to be discreet and challenging to identify, it isn't identical to a standard HTTPS connection." + "However, certain traffic analysis systems might still detect a Shadowsocks connection. " + "Due to limited support in Amnezia, it's recommended to use AmneziaWG protocol.\n\n" + "* Available in the AmneziaVPN only on desktop platforms\n" + "* Normal power consumption on mobile devices\n\n" + "* Configurable encryption protocol\n" + "* Detectable by some DPI systems\n" + "* Works over TCP network protocol.") }, { DockerContainer::Cloak, QObject::tr("This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for " "blocking protection.\n\n" @@ -157,34 +163,53 @@ QMap ContainerProps::containerDetailedDescriptions() "Immediately after receiving the first data packet, Cloak authenticates the incoming connection. " "If authentication fails, the plugin masks the server as a fake website and your VPN becomes " "invisible to analysis systems.\n\n" - "If there is a high level of Internet censorship in your region, we advise you to use only " - "OpenVPN over Cloak from the first connection\n" + "If there is a extreme level of Internet censorship in your region, we advise you to use only " + "OpenVPN over Cloak from the first connection\n\n" + "* Available in the AmneziaVPN across all platforms\n" "* High power consumption on mobile devices\n" "* Flexible settings\n" "* Not recognised by DPI analysis systems\n" - "* Works via TCP network protocol\n") }, + "* Works over TCP network protocol, 443 port.\n") }, { DockerContainer::WireGuard, QObject::tr("A relatively new popular VPN protocol with a simplified architecture.\n" "Provides stable VPN connection, high performance on all devices. Uses hard-coded encryption " "settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput.\n" - - "* Low power consumption on mobile devices.\n" - "* Minimum number of settings.\n" - "* Easily recognised by DPI analysis systems, susceptible to blocking.\n" - "* Works via UDP network protocol.\n") }, - { DockerContainer::Awg, QObject::tr("AmneziaWG container") }, + "WireGuard is very susceptible to blocking due to its distinct packet signatures. " + "Unlike some other VPN protocols that employ obfuscation techniques, " + "the consistent signature patterns of WireGuard packets can be more easily identified and " + "thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools.\n\n" + "* Available in the AmneziaVPN across all platforms\n" + "* Low power consumption\n" + "* Minimum number of settings\n" + "* Easily recognised by DPI analysis systems, susceptible to blocking\n" + "* Works over UDP network protocol.") }, + { DockerContainer::Awg, + QObject::tr("A modern iteration of the popular VPN protocol, " + "AmneziaWG builds upon the foundation set by WireGuard, " + "retaining its simplified architecture and high-performance capabilities across devices.\n" + "While WireGuard is known for its efficiency, " + "it had issues with being easily detected due to its distinct packet signatures. " + "AmneziaWG solves this problem by using better obfuscation methods, " + "making its traffic blend in with regular internet traffic.\n" + "This means that AmneziaWG keeps the fast performance of the original " + "while adding an extra layer of stealth, " + "making it a great choice for those wanting a fast and discreet VPN connection.\n\n" + "* Available in the AmneziaVPN across all platforms\n" + "* Low power consumption\n" + "* Minimum number of settings\n" + "* Not recognised by DPI analysis systems, resistant to blocking\n" + "* Works over UDP network protocol.") }, { DockerContainer::Ipsec, - QObject::tr("A modern stable protocol.\n\n" - - "IKEv2 with IPSec encryption layer. Transmits data over fixed UDP ports 500 and 4500 protecting " - "them with strong 3DES and AES crypto algorithms. Allows very fast switching between networks " - "and devices. Due to its security, stability and speed, IKEv2 is currently one of the best VPN " - "solutions for mobile devices. Vulnerable to detection and blocking.\n" - + QObject::tr("IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol.\n" + "One of its distinguishing features is its ability to swiftly switch between networks and devices, " + "making it particularly adaptive in dynamic network environments. \n" + "While it offers a blend of security, stability, and speed, " + "it's essential to note that IKEv2 can be easily detected and is susceptible to blocking.\n\n" + "* Available in the AmneziaVPN only on Windows\n" "* Low power consumption, on mobile devices\n" - "* Minimal configuration.\n" - "* Recognised by DPI analysis systems.\n" - "* Works only over UDP network protocol\n") }, + "* Minimal configuration\n" + "* Recognised by DPI analysis systems\n" + "* Works over UDP network protocol, ports 500 and 4500.") }, { DockerContainer::TorWebSite, QObject::tr("Website in Tor network") }, { DockerContainer::Dns, QObject::tr("DNS Service") }, diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index d3eef897..7ba02dcb 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -2414,74 +2414,7 @@ and will not be shared or disclosed to the Amnezia or any third parties IPsec - - The time-tested most popular VPN protocol. - -Uses a proprietary security protocol with SSL/TLS for encryption and key exchange and supports various authentication methods, making it suitable for a variety of devices and operating systems. - -* Normal power consumption on mobile devices -* Flexible customisation to suit user needs to work with different operating systems and devices. -* Recognised by DPI analysis systems and therefore susceptible to blocking. -* Can operate over both TCP and UDP network protocols. - - - - - Based on the SOCKS5 proxy protocol, which protects the connection using the AEAD cipher - roughly along the same lines as SSH tunnelling. A Shadowsocks connection is difficult to identify because it is virtually identical to a normal HTTPS connection. - -However, some traffic analysis systems can still recognise a ShadowSocks connection, so in countries with high levels of censorship we recommend using OpenVPN in conjunction with Cloak. -* Average power consumption on mobile devices (higher than OpenVPN). -* It is possible to configure the encryption protocol. -* Recognised by some DPI analysis systems -* Works only via TCP network protocol - - - - - - This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for blocking protection. - -OpenVPN provides a secure VPN connection by encrypting all Internet traffic between the client and the server. - -Cloak protects OpenVPN from detection and blocking. - -Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, and also protects the VPN from detection by Active Probing. This makes it very resistant to being detected - -Immediately after receiving the first data packet, Cloak authenticates the incoming connection. If authentication fails, the plugin masks the server as a fake website and your VPN becomes invisible to analysis systems. - -If there is a high level of Internet censorship in your region, we advise you to use only OpenVPN over Cloak from the first connection -* High power consumption on mobile devices -* Flexible settings -* Not recognised by DPI analysis systems -* Works via TCP network protocol - - - - - - A relatively new popular VPN protocol with a simplified architecture. -Provides stable VPN connection, high performance on all devices. Uses hard-coded encryption settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput. -* Low power consumption on mobile devices. -* Minimum number of settings. -* Easily recognised by DPI analysis systems, susceptible to blocking. -* Works via UDP network protocol. - - - - - - A modern stable protocol. - -IKEv2 with IPSec encryption layer. Transmits data over fixed UDP ports 500 and 4500 protecting them with strong 3DES and AES crypto algorithms. Allows very fast switching between networks and devices. Due to its security, stability and speed, IKEv2 is currently one of the best VPN solutions for mobile devices. Vulnerable to detection and blocking. -* Low power consumption, on mobile devices -* Minimal configuration. -* Recognised by DPI analysis systems. -* Works only over UDP network protocol - - - - - + DNS Service DNS Сервис @@ -2492,7 +2425,7 @@ IKEv2 with IPSec encryption layer. Transmits data over fixed UDP ports 500 and 4 - + Website in Tor network Веб-сайт в сети Tor @@ -2547,12 +2480,96 @@ IKEv2 with IPSec encryption layer. Transmits data over fixed UDP ports 500 and 4 Создайте на сервере файловое хранилище для безопасного хранения и передачи файлов. - - AmneziaWG container - AmneziaWG протокол + + OpenVPN stands as one of the most popular and time-tested VPN protocols available. +It employs its unique security protocol, leveraging the strength of SSL/TLS for encryption and key exchange. Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, catering to a wide range of devices and operating systems. Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, which continually reinforces its security. With a strong balance of performance, security, and compatibility, OpenVPN remains a top choice for privacy-conscious individuals and businesses alike. + +* Available in the AmneziaVPN across all platforms +* Normal power consumption on mobile devices +* Flexible customisation to suit user needs to work with different operating systems and devices +* Recognised by DPI analysis systems and therefore susceptible to blocking +* Can operate over both TCP and UDP network protocols. + - + + Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. Although Shadowsocks is designed to be discreet and challenging to identify, it isn't identical to a standard HTTPS connection.However, certain traffic analysis systems might still detect a Shadowsocks connection. Due to limited support in Amnezia, it's recommended to use AmneziaWG protocol. + +* Available in the AmneziaVPN only on desktop platforms +* Normal power consumption on mobile devices + +* Configurable encryption protocol +* Detectable by some DPI systems +* Works over TCP network protocol. + + + + + This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for blocking protection. + +OpenVPN provides a secure VPN connection by encrypting all Internet traffic between the client and the server. + +Cloak protects OpenVPN from detection and blocking. + +Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, and also protects the VPN from detection by Active Probing. This makes it very resistant to being detected + +Immediately after receiving the first data packet, Cloak authenticates the incoming connection. If authentication fails, the plugin masks the server as a fake website and your VPN becomes invisible to analysis systems. + +If there is a extreme level of Internet censorship in your region, we advise you to use only OpenVPN over Cloak from the first connection + +* Available in the AmneziaVPN across all platforms +* High power consumption on mobile devices +* Flexible settings +* Not recognised by DPI analysis systems +* Works over TCP network protocol, 443 port. + + + + + + A relatively new popular VPN protocol with a simplified architecture. +Provides stable VPN connection, high performance on all devices. Uses hard-coded encryption settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput. +WireGuard is very susceptible to blocking due to its distinct packet signatures. Unlike some other VPN protocols that employ obfuscation techniques, the consistent signature patterns of WireGuard packets can be more easily identified and thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools. + +* Available in the AmneziaVPN across all platforms +* Low power consumption +* Minimum number of settings +* Easily recognised by DPI analysis systems, susceptible to blocking +* Works over UDP network protocol. + + + + + A modern iteration of the popular VPN protocol, AmneziaWG builds upon the foundation set by WireGuard, retaining its simplified architecture and high-performance capabilities across devices. +While WireGuard is known for its efficiency, it had issues with being easily detected due to its distinct packet signatures. AmneziaWG solves this problem by using better obfuscation methods, making its traffic blend in with regular internet traffic. +This means that AmneziaWG keeps the fast performance of the original while adding an extra layer of stealth, making it a great choice for those wanting a fast and discreet VPN connection. + +* Available in the AmneziaVPN across all platforms +* Low power consumption +* Minimum number of settings +* Not recognised by DPI analysis systems, resistant to blocking +* Works over UDP network protocol. + + + + + A modern stable protocol. + +IKEv2 with IPSec encryption layer. Transmits data over fixed UDP ports 500 and 4500 protecting them with strong 3DES and AES crypto algorithms. Allows very fast switching between networks and devices. Due to its security, stability and speed, IKEv2 is currently one of the best VPN solutions for mobile devices. Vulnerable to detection and blocking. + +* Available in the AmneziaVPN only on Windows +* Low power consumption, on mobile devices +* Minimal configuration +* Recognised by DPI analysis systems +* Works over UDP network protocol + + + + AmneziaWG container + AmneziaWG протокол + + + Sftp file sharing service - is secure FTP service Сервис обмена файлами Sftp - безопасный FTP-сервис @@ -2839,32 +2856,32 @@ IKEv2 with IPSec encryption layer. Transmits data over fixed UDP ports 500 and 4 amnezia::ContainerProps - + Low Низкий - + Medium or High Спедний или Высокий - + Extreme Экстремальный - + I just want to increase the level of my privacy. Я просто хочу повысить уровень своей приватности. - + I want to bypass censorship. This option recommended in most cases. Я хочу обойти блокировки. Этот вариант рекомендуется в большинстве случаев. - + Most VPN protocols are blocked. Recommended if other options are not working. Большинство VPN протоколов заблокированы. Рекомендуется, если другие варианты не работают. diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index be519a91..d68981c4 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -2537,7 +2537,7 @@ and will not be shared or disclosed to the Amnezia or any third parties - + Website in Tor network 在 Tor 网络中架设网站 @@ -2598,30 +2598,30 @@ and will not be shared or disclosed to the Amnezia or any third parties - The time-tested most popular VPN protocol. - -Uses a proprietary security protocol with SSL/TLS for encryption and key exchange and supports various authentication methods, making it suitable for a variety of devices and operating systems. + OpenVPN stands as one of the most popular and time-tested VPN protocols available. +It employs its unique security protocol, leveraging the strength of SSL/TLS for encryption and key exchange. Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, catering to a wide range of devices and operating systems. Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, which continually reinforces its security. With a strong balance of performance, security, and compatibility, OpenVPN remains a top choice for privacy-conscious individuals and businesses alike. +* Available in the AmneziaVPN across all platforms * Normal power consumption on mobile devices -* Flexible customisation to suit user needs to work with different operating systems and devices. -* Recognised by DPI analysis systems and therefore susceptible to blocking. +* Flexible customisation to suit user needs to work with different operating systems and devices +* Recognised by DPI analysis systems and therefore susceptible to blocking * Can operate over both TCP and UDP network protocols. - - Based on the SOCKS5 proxy protocol, which protects the connection using the AEAD cipher - roughly along the same lines as SSH tunnelling. A Shadowsocks connection is difficult to identify because it is virtually identical to a normal HTTPS connection. + + Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. Although Shadowsocks is designed to be discreet and challenging to identify, it isn't identical to a standard HTTPS connection.However, certain traffic analysis systems might still detect a Shadowsocks connection. Due to limited support in Amnezia, it's recommended to use AmneziaWG protocol. -However, some traffic analysis systems can still recognise a ShadowSocks connection, so in countries with high levels of censorship we recommend using OpenVPN in conjunction with Cloak. -* Average power consumption on mobile devices (higher than OpenVPN). -* It is possible to configure the encryption protocol. -* Recognised by some DPI analysis systems -* Works only via TCP network protocol - +* Available in the AmneziaVPN only on desktop platforms +* Normal power consumption on mobile devices + +* Configurable encryption protocol +* Detectable by some DPI systems +* Works over TCP network protocol. - + This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for blocking protection. OpenVPN provides a secure VPN connection by encrypting all Internet traffic between the client and the server. @@ -2632,35 +2632,53 @@ Cloak can modify packet metadata so that it completely masks VPN traffic as norm Immediately after receiving the first data packet, Cloak authenticates the incoming connection. If authentication fails, the plugin masks the server as a fake website and your VPN becomes invisible to analysis systems. -If there is a high level of Internet censorship in your region, we advise you to use only OpenVPN over Cloak from the first connection +If there is a extreme level of Internet censorship in your region, we advise you to use only OpenVPN over Cloak from the first connection + +* Available in the AmneziaVPN across all platforms * High power consumption on mobile devices * Flexible settings * Not recognised by DPI analysis systems -* Works via TCP network protocol +* Works over TCP network protocol, 443 port. - + A relatively new popular VPN protocol with a simplified architecture. Provides stable VPN connection, high performance on all devices. Uses hard-coded encryption settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput. -* Low power consumption on mobile devices. -* Minimum number of settings. -* Easily recognised by DPI analysis systems, susceptible to blocking. -* Works via UDP network protocol. - +WireGuard is very susceptible to blocking due to its distinct packet signatures. Unlike some other VPN protocols that employ obfuscation techniques, the consistent signature patterns of WireGuard packets can be more easily identified and thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools. + +* Available in the AmneziaVPN across all platforms +* Low power consumption +* Minimum number of settings +* Easily recognised by DPI analysis systems, susceptible to blocking +* Works over UDP network protocol. - + + A modern iteration of the popular VPN protocol, AmneziaWG builds upon the foundation set by WireGuard, retaining its simplified architecture and high-performance capabilities across devices. +While WireGuard is known for its efficiency, it had issues with being easily detected due to its distinct packet signatures. AmneziaWG solves this problem by using better obfuscation methods, making its traffic blend in with regular internet traffic. +This means that AmneziaWG keeps the fast performance of the original while adding an extra layer of stealth, making it a great choice for those wanting a fast and discreet VPN connection. + +* Available in the AmneziaVPN across all platforms +* Low power consumption +* Minimum number of settings +* Not recognised by DPI analysis systems, resistant to blocking +* Works over UDP network protocol. + + + + A modern stable protocol. IKEv2 with IPSec encryption layer. Transmits data over fixed UDP ports 500 and 4500 protecting them with strong 3DES and AES crypto algorithms. Allows very fast switching between networks and devices. Due to its security, stability and speed, IKEv2 is currently one of the best VPN solutions for mobile devices. Vulnerable to detection and blocking. + +* Available in the AmneziaVPN only on Windows * Low power consumption, on mobile devices -* Minimal configuration. -* Recognised by DPI analysis systems. -* Works only over UDP network protocol - +* Minimal configuration +* Recognised by DPI analysis systems +* Works over UDP network protocol @@ -2679,22 +2697,17 @@ IKEv2 with IPSec encryption layer. Transmits data over fixed UDP ports 500 and 4 WireGuard container WireGuard 容器 - - - AmneziaWG container - - IPsec container IPsec 容器 - + DNS Service DNS 服务 - + Sftp file sharing service - is secure FTP service Sftp 文件共享服务 - 安全的 FTP 服务 @@ -2980,32 +2993,32 @@ IKEv2 with IPSec encryption layer. Transmits data over fixed UDP ports 500 and 4 amnezia::ContainerProps - + Low - + Medium or High - + Extreme - + I just want to increase the level of my privacy. - + I want to bypass censorship. This option recommended in most cases. - + Most VPN protocols are blocked. Recommended if other options are not working. From 4c81cdb4a2397ab9a0dc1e45975feca75ca0155b Mon Sep 17 00:00:00 2001 From: pokamest Date: Sun, 15 Oct 2023 12:29:41 +0100 Subject: [PATCH 230/278] Update translation --- client/translations/amneziavpn_ru.ts | 58 ++++++++++++++-------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index d2ad31b7..3d7766a3 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -340,7 +340,7 @@ Already installed containers were found on the server. All installed containers Remove AmneziaWG - Remove AmneziaWG + Удалить AmneziaWG @@ -350,7 +350,7 @@ Already installed containers were found on the server. All installed containers All users who you shared a connection with will no longer be able to connect to it. - Все пользователи, которым вы поделились VPN с этим протоколом, больше не смогут к нему подключаться. + Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. @@ -383,13 +383,13 @@ Already installed containers were found on the server. All installed containers Port - Port + Порт Cipher - Cipher + Шифрование @@ -484,7 +484,7 @@ Already installed containers were found on the server. All installed containers Cipher - Шифрованаие + Шифрование @@ -575,7 +575,7 @@ Already installed containers were found on the server. All installed containers All users who you shared a connection with will no longer be able to connect to it. - Все пользователи, которым вы поделились VPN, больше не смогут к нему подключаться. + Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. @@ -623,7 +623,7 @@ Already installed containers were found on the server. All installed containers All users who you shared a connection with will no longer be able to connect to it. - Все пользователи, которым вы поделились VPN с этим протоколом больше не смогут к нему подключаться. + Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. @@ -679,7 +679,7 @@ Already installed containers were found on the server. All installed containers The DNS address is the same as the address of your server. You can configure DNS in the settings, under the connections tab. - Адрес DNS совпадает с адресом вашего сервера. Настроить DNS можно в настройках, во вкладке "Соединения". + Адрес DNS совпадает с адресом вашего сервера. Настроить DNS можно во вкладке "Соединения" настроек приложения @@ -772,7 +772,7 @@ Already installed containers were found on the server. All installed containers Remove SFTP and all data stored there - Удалите SFTP-хранилище со всеми данными + Удалить SFTP-хранилище со всеми данными @@ -820,7 +820,7 @@ Already installed containers were found on the server. All installed containers After installation it takes several minutes while your onion site will become available in the Tor Network. - Через несколько минут после установки ваш Onion сайт станет доступен в сети Tor. + Через несколько минут после установки ваш Onion сайт станет доступен в сети Tor. @@ -835,7 +835,7 @@ Already installed containers were found on the server. All installed containers The site with all data will be removed from the tor network. - Сайт со всеми данными будет удален из сети tor. + Сайт со всеми данными будет удален из сети Tor. @@ -891,7 +891,7 @@ Already installed containers were found on the server. All installed containers Support the project with a donation - Поддержите проект донатами + Поддержите проект пожертвованием @@ -999,12 +999,12 @@ Already installed containers were found on the server. All installed containers Start minimized - Запуск в свернутом виде + Запускать в свернутом виде Launch application minimized - Запуск приложения в свернутом виде + Запускать приложение в свернутом виде @@ -1039,7 +1039,7 @@ Already installed containers were found on the server. All installed containers All settings will be reset to default. All installed AmneziaVPN services will still remain on the server. - Все данные из приложения будут удалены Все установленные сервисы AmneziaVPN останутся на сервере. + Все данные из приложения будут удалены, все установленные сервисы AmneziaVPN останутся на сервере. @@ -1267,7 +1267,7 @@ Already installed containers were found on the server. All installed containers Save logs to file - Сохранить логи в файл + Сохранять логи в файл @@ -1344,7 +1344,7 @@ Already installed containers were found on the server. All installed containers Check the server for previously installed Amnezia services - Проверка сервера на наличие ранее установленных сервисов Amnezia + Проверить сервер на наличие ранее установленных сервисов Amnezia @@ -1369,7 +1369,7 @@ Already installed containers were found on the server. All installed containers Clear server from Amnezia software - Очистка сервера от протоколов и сервисов Amnezia + Очистить сервер от протоколов и сервисов Amnezia @@ -1379,7 +1379,7 @@ Already installed containers were found on the server. All installed containers All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted. - На сервере будут удалены все, что связанно с Amnezia: протоколы сервисы конфигурационные файлы, ключи и сертификаты. + На сервере будут удалены все данные, связанные с Amnezia: протоколы, сервисы, конфигурационные файлы, ключи и сертификаты. @@ -1466,7 +1466,7 @@ Already installed containers were found on the server. All installed containers Split tunneling - Раздельно VPN-туннелирование + Раздельное VPN-туннелирование @@ -1539,7 +1539,7 @@ Already installed containers were found on the server. All installed containers Add imported sites to existing ones - Добавление импортированных сайтов к существующим + Добавить импортированные сайты к существующим @@ -1561,7 +1561,7 @@ It's okay as long as it's from someone you trust. What do you have? - Выберете что у вас есть? + Выберете что у вас есть @@ -1657,7 +1657,7 @@ It's okay as long as it's from someone you trust. I want to choose a VPN protocol - Выбор VPN-протокола + Выбрать VPN-протокол @@ -1709,7 +1709,7 @@ It's okay as long as it's from someone you trust. Installing %1 - Установка %1 + Установить %1 @@ -1734,7 +1734,7 @@ It's okay as long as it's from someone you trust. Install - Установка + Установить @@ -1747,7 +1747,7 @@ It's okay as long as it's from someone you trust. Choose the one with the highest priority for you. Later, you can install other protocols and additional services, such as DNS proxy and SFTP. - Выберите протокол, который вам больше подходит . В дальнейшем можно установить другие протоколы и дополнительные сервисы, такие как DNS-прокси и SFTP. + Выберите протокол, который вам больше подходит. В дальнейшем можно установить другие протоколы и дополнительные сервисы, такие как DNS-прокси, TOR-сайт и SFTP. @@ -1806,7 +1806,7 @@ It's okay as long as it's from someone you trust. Insert - Вставка + Вставить @@ -2452,7 +2452,7 @@ It's okay as long as it's from someone you trust. Replace the current DNS server with your own. This will increase your privacy level. - Замените адрес DNS-сервера на собственный. Это повысит уровень конфиденциальности. + Замените DNS-сервер на Amnezia DNS. Это повысит уровень конфиденциальности. @@ -2565,7 +2565,7 @@ It's okay as long as it's from someone you trust. Choose language - Выберете язык + Выберите язык From 5f5435c6458fc879fe9ee99333c1ab8a47fac4fd Mon Sep 17 00:00:00 2001 From: pokamest Date: Sun, 15 Oct 2023 12:41:01 +0100 Subject: [PATCH 231/278] Updated ru ts --- client/translations/amneziavpn_ru.ts | 44 ++++++++++++------------- client/translations/amneziavpn_zh_CN.ts | 26 +++++++-------- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index 6f80bfc3..7bb59a7e 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -2414,7 +2414,20 @@ and will not be shared or disclosed to the Amnezia or any third parties IPsec - + + IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol. +One of its distinguishing features is its ability to swiftly switch between networks and devices, making it particularly adaptive in dynamic network environments. +While it offers a blend of security, stability, and speed, it's essential to note that IKEv2 can be easily detected and is susceptible to blocking. + +* Available in the AmneziaVPN only on Windows +* Low power consumption, on mobile devices +* Minimal configuration +* Recognised by DPI analysis systems +* Works over UDP network protocol, ports 500 and 4500. + + + + DNS Service DNS Сервис @@ -2425,7 +2438,7 @@ and will not be shared or disclosed to the Amnezia or any third parties - + Website in Tor network Веб-сайт в сети Tor @@ -2551,25 +2564,12 @@ This means that AmneziaWG keeps the fast performance of the original while addin * Works over UDP network protocol. - - - A modern stable protocol. - -IKEv2 with IPSec encryption layer. Transmits data over fixed UDP ports 500 and 4500 protecting them with strong 3DES and AES crypto algorithms. Allows very fast switching between networks and devices. Due to its security, stability and speed, IKEv2 is currently one of the best VPN solutions for mobile devices. Vulnerable to detection and blocking. - -* Available in the AmneziaVPN only on Windows -* Low power consumption, on mobile devices -* Minimal configuration -* Recognised by DPI analysis systems -* Works over UDP network protocol - - AmneziaWG container AmneziaWG протокол - + Sftp file sharing service - is secure FTP service Сервис обмена файлами Sftp - безопасный FTP-сервис @@ -2856,32 +2856,32 @@ IKEv2 with IPSec encryption layer. Transmits data over fixed UDP ports 500 and 4 amnezia::ContainerProps - + Low Низкий - + Medium or High Спедний или Высокий - + Extreme Экстремальный - + I just want to increase the level of my privacy. Я просто хочу повысить уровень своей приватности. - + I want to bypass censorship. This option recommended in most cases. Я хочу обойти блокировки. Этот вариант рекомендуется в большинстве случаев. - + Most VPN protocols are blocked. Recommended if other options are not working. Большинство VPN протоколов заблокированы. Рекомендуется, если другие варианты не работают. diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index d68981c4..576112de 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -2537,7 +2537,7 @@ and will not be shared or disclosed to the Amnezia or any third parties - + Website in Tor network 在 Tor 网络中架设网站 @@ -2670,15 +2670,15 @@ This means that AmneziaWG keeps the fast performance of the original while addin - A modern stable protocol. - -IKEv2 with IPSec encryption layer. Transmits data over fixed UDP ports 500 and 4500 protecting them with strong 3DES and AES crypto algorithms. Allows very fast switching between networks and devices. Due to its security, stability and speed, IKEv2 is currently one of the best VPN solutions for mobile devices. Vulnerable to detection and blocking. + IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol. +One of its distinguishing features is its ability to swiftly switch between networks and devices, making it particularly adaptive in dynamic network environments. +While it offers a blend of security, stability, and speed, it's essential to note that IKEv2 can be easily detected and is susceptible to blocking. * Available in the AmneziaVPN only on Windows * Low power consumption, on mobile devices * Minimal configuration * Recognised by DPI analysis systems -* Works over UDP network protocol +* Works over UDP network protocol, ports 500 and 4500. @@ -2702,12 +2702,12 @@ IKEv2 with IPSec encryption layer. Transmits data over fixed UDP ports 500 and 4 IPsec 容器 - + DNS Service DNS 服务 - + Sftp file sharing service - is secure FTP service Sftp 文件共享服务 - 安全的 FTP 服务 @@ -2993,32 +2993,32 @@ IKEv2 with IPSec encryption layer. Transmits data over fixed UDP ports 500 and 4 amnezia::ContainerProps - + Low - + Medium or High - + Extreme - + I just want to increase the level of my privacy. - + I want to bypass censorship. This option recommended in most cases. - + Most VPN protocols are blocked. Recommended if other options are not working. From a01ba5909c965324beab6e0e365d6aef0378f09d Mon Sep 17 00:00:00 2001 From: pokamest Date: Sun, 15 Oct 2023 12:53:44 +0100 Subject: [PATCH 232/278] Version bump --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index af4ae898..85d7d0ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.0.8.1 +project(${PROJECT} VERSION 4.0.8.2 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) From cb5c09d9679753785ecf3159e23a66bd877c9773 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Sun, 15 Oct 2023 21:29:01 +0800 Subject: [PATCH 233/278] adapted questionDrawer --- client/ui/qml/Components/QuestionDrawer.qml | 3 ++- client/ui/qml/Components/ShareConnectionDrawer.qml | 2 +- client/ui/qml/Pages2/PageSettingsBackup.qml | 1 + client/ui/qml/Pages2/PageSettingsServerData.qml | 2 ++ client/ui/qml/Pages2/PageSettingsServerProtocol.qml | 1 + 5 files changed, 7 insertions(+), 2 deletions(-) diff --git a/client/ui/qml/Components/QuestionDrawer.qml b/client/ui/qml/Components/QuestionDrawer.qml index edc8df9e..72067f97 100644 --- a/client/ui/qml/Components/QuestionDrawer.qml +++ b/client/ui/qml/Components/QuestionDrawer.qml @@ -15,10 +15,11 @@ Drawer2Type { property var yesButtonFunction property var noButtonFunction + property real drawerHeight: 0.5 width: parent.width height: parent.height - contentHeight: parent.height * 0.5 + contentHeight: parent.height * drawerHeight ColumnLayout { parent: root.contentParent diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 22c567a8..ae0df0e2 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -31,7 +31,7 @@ Drawer2Type { height: parent.height contentHeight: parent.height * 0.9 - onClose: { + onClosed: { configExtension = ".vpn" configCaption = qsTr("Save AmneziaVPN config") configFileName = "amnezia_config" diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml index 1fb160a1..2caaf914 100644 --- a/client/ui/qml/Pages2/PageSettingsBackup.qml +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -151,5 +151,6 @@ PageType { QuestionDrawer { id: questionDrawer + parent: root } } diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index b130eef9..2e401c67 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -193,6 +193,8 @@ PageType { QuestionDrawer { id: questionDrawer + drawerHeight: 0.8 + parent: root } } diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml index d1c8b22b..a518167c 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -141,6 +141,7 @@ PageType { QuestionDrawer { id: questionDrawer + parent: root } } } From 7bd1340190abd10964731d66228ba671b3f87aaf Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sun, 15 Oct 2023 20:41:49 +0500 Subject: [PATCH 234/278] fixed display of sites on page split tunneling --- client/amnezia_application.cpp | 2 +- client/translations/amneziavpn_ru.ts | 44 ++++++++++++------- client/translations/amneziavpn_zh_CN.ts | 44 ++++++++++++------- client/ui/models/sites_model.cpp | 22 +++++++++- client/ui/models/sites_model.h | 4 ++ .../ui/qml/Pages2/PageSettingsConnection.qml | 2 +- .../qml/Pages2/PageSettingsSplitTunneling.qml | 15 ++----- 7 files changed, 85 insertions(+), 48 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 4e6bce2b..229aa7b8 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -297,7 +297,7 @@ void AmneziaApplication::initModels() connect(m_containersModel.get(), &ContainersModel::defaultContainerChanged, this, [this]() { if (m_containersModel->getDefaultContainer() == DockerContainer::WireGuard && m_sitesModel->getRouteMode() != Settings::RouteMode::VpnAllSites) { - m_sitesModel->setRouteMode(Settings::RouteMode::VpnAllSites); + m_sitesModel->toggleSplitTunneling(false); emit m_pageController->showNotificationMessage( tr("Split tunneling for WireGuard is not implemented, the option was disabled")); } diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index d3eef897..14973fbd 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -1471,75 +1471,75 @@ Already installed containers were found on the server. All installed containers Раздельно VPN-туннелирование - + Mode Режим - + Remove Удалить - + Continue Продолжить - + Cancel Отменить - + Site or IP Сайт или IP - + Import/Export Sites Импорт/экспорт Сайтов - + Import Импорт - + Save site list Сохранить список сайтов - + Save sites Сохранить - - - + + + Sites files (*.json) Sites files (*.json) - + Import a list of sites Импортировать список с сайтами - + Replace site list Заменить список сайтов - - + + Open sites file Открыть список с сайтами - + Add imported sites to existing ones Добавление импортированных сайтов к существующим @@ -2621,6 +2621,16 @@ IKEv2 with IPSec encryption layer. Transmits data over fixed UDP ports 500 and 4 error 0x%1: %2 error 0x%1: %2 + + + WireGuard Configuration Highlighter + + + + + &Randomize colors + + SelectLanguageDrawer diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index be519a91..b439deaa 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -1573,75 +1573,75 @@ And if you don't like the app, all the more support it - the donation will 隧道分离 - + Mode 规则 - + Remove 移除 - + Continue 继续 - + Cancel 取消 - + Site or IP 网站或IP地址 - + Import/Export Sites 导入/导出网站 - + Import 导入 - + Save site list 保存网址 - + Save sites 保存网址 - - - + + + Sites files (*.json) - + Import a list of sites 导入网址列表 - + Replace site list 替换网址列表 - - + + Open sites file 打开网址文件 - + Add imported sites to existing ones 将导入的网址添加到现有网址中 @@ -2758,6 +2758,16 @@ IKEv2 with IPSec encryption layer. Transmits data over fixed UDP ports 500 and 4 error 0x%1: %2 错误 0x%1: %2 + + + WireGuard Configuration Highlighter + + + + + &Randomize colors + + SelectLanguageDrawer diff --git a/client/ui/models/sites_model.cpp b/client/ui/models/sites_model.cpp index 5fd9a38b..1e0f1692 100644 --- a/client/ui/models/sites_model.cpp +++ b/client/ui/models/sites_model.cpp @@ -3,7 +3,13 @@ SitesModel::SitesModel(std::shared_ptr settings, QObject *parent) : QAbstractListModel(parent), m_settings(settings) { - m_currentRouteMode = m_settings->routeMode(); + auto routeMode = m_settings->routeMode(); + if (routeMode == Settings::RouteMode::VpnAllSites) { + m_isSplitTunnelingEnabled = false; + m_currentRouteMode = Settings::RouteMode::VpnOnlyForwardSites; + } else { + m_currentRouteMode = routeMode; + } fillSites(); } @@ -93,6 +99,20 @@ void SitesModel::setRouteMode(int routeMode) emit routeModeChanged(); } +bool SitesModel::isSplitTunnelingEnabled() +{ + return m_isSplitTunnelingEnabled; +} + +void SitesModel::toggleSplitTunneling(bool enabled) +{ + if (enabled) { + setRouteMode(m_currentRouteMode); + } else { + m_settings->setRouteMode(Settings::RouteMode::VpnAllSites); + } +} + QVector > SitesModel::getCurrentSites() { return m_sites; diff --git a/client/ui/models/sites_model.h b/client/ui/models/sites_model.h index 70def0ec..ad16b7a3 100644 --- a/client/ui/models/sites_model.h +++ b/client/ui/models/sites_model.h @@ -31,6 +31,9 @@ public slots: int getRouteMode(); void setRouteMode(int routeMode); + bool isSplitTunnelingEnabled(); + void toggleSplitTunneling(bool enabled); + QVector> getCurrentSites(); signals: @@ -44,6 +47,7 @@ private: std::shared_ptr m_settings; + bool m_isSplitTunnelingEnabled; Settings::RouteMode m_currentRouteMode; QVector> m_sites; diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml index 7f0262f9..b5b1bd97 100644 --- a/client/ui/qml/Pages2/PageSettingsConnection.qml +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -112,7 +112,7 @@ PageType { } LabelWithButtonType { - visible: !GC.isMobile() + visible: false//!GC.isMobile() Layout.fillWidth: true diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index cc4973f1..d7f77871 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -93,22 +93,15 @@ PageType { SwitcherType { id: switcher - property int lastActiveRouteMode: routeMode.onlyForwardSites - enabled: root.pageEnabled Layout.fillWidth: true Layout.rightMargin: 16 - checked: SitesModel.routeMode !== routeMode.allSites - onToggled: { - if (checked) { - SitesModel.routeMode = lastActiveRouteMode - } else { - lastActiveRouteMode = SitesModel.routeMode - selector.text = root.routeModesModel[getRouteModesModelIndex()].name - SitesModel.routeMode = routeMode.allSites - } + checked: SitesModel.isSplitTunnelingEnabled() + onToggled: { + SitesModel.toggleSplitTunneling(checked) + selector.text = root.routeModesModel[getRouteModesModelIndex()].name } } } From 2a4a01a4bec1e73dbfd7a9be6fc1cacfa0ede2b8 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 16 Oct 2023 13:28:37 +0500 Subject: [PATCH 235/278] removed split site tunneling page blocking when switcher is turned off --- client/ui/qml/Pages2/PageSettingsSplitTunneling.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index d7f77871..873ae997 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -116,7 +116,7 @@ PageType { drawerHeight: 0.4375 - enabled: switcher.checked && root.pageEnabled + enabled: root.pageEnabled headerText: qsTr("Mode") @@ -158,7 +158,7 @@ PageType { anchors.topMargin: 16 contentHeight: col.implicitHeight + addSiteButton.implicitHeight + addSiteButton.anchors.bottomMargin + addSiteButton.anchors.topMargin - enabled: switcher.checked && root.pageEnabled + enabled: root.pageEnabled Column { id: col From 221d45f564d10347e50b612813c60e38eee6168c Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 16 Oct 2023 13:32:56 +0500 Subject: [PATCH 236/278] fixed pageSettingsDns width --- client/ui/qml/Pages2/PageSettingsDns.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ui/qml/Pages2/PageSettingsDns.qml b/client/ui/qml/Pages2/PageSettingsDns.qml index 58ec0783..5670464f 100644 --- a/client/ui/qml/Pages2/PageSettingsDns.qml +++ b/client/ui/qml/Pages2/PageSettingsDns.qml @@ -46,6 +46,7 @@ PageType { } ParagraphTextType { + Layout.fillWidth: true text: qsTr("If AmneziaDNS is not used or installed") } From 8e0eef3316c5f0ed90cd044dea514c4b284821a6 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 16 Oct 2023 13:40:43 +0500 Subject: [PATCH 237/278] fixed selection of default container after installing a new container --- client/ui/controllers/installController.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 6eec9c6b..bb10d39c 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -184,7 +184,7 @@ void InstallController::installContainer(DockerContainer container, QJsonObject } if (ContainerProps::containerService(container) == ServiceType::Vpn) { - m_containersModel->setData(m_containersModel->index(0, 0), container, ContainersModel::Roles::IsDefaultRole); + m_containersModel->setData(m_containersModel->index(container), true, ContainersModel::Roles::IsDefaultRole); } emit installContainerFinished(finishMessage, ContainerProps::containerService(container) == ServiceType::Other); return; From cdb18de305771e6a5c2f6e1c48ad1ab1db0392f7 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 16 Oct 2023 13:43:27 +0500 Subject: [PATCH 238/278] brought back the ability to share wireguard native format configs --- client/ui/qml/Pages2/PageShare.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index aa04a1fe..25aad3de 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -320,7 +320,7 @@ PageType { if (index === ContainerProps.containerFromString("amnezia-openvpn")) { root.connectionTypesModel.push(openVpnConnectionFormat) - } else if (index === ContainerProps.containerFromString("amnezia-awg")) { + } else if (index === ContainerProps.containerFromString("amnezia-wireguard")) { root.connectionTypesModel.push(wireGuardConnectionFormat) } } From e01b1db706501d499f29c7f832d8791db2cd3f45 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 16 Oct 2023 14:34:03 +0500 Subject: [PATCH 239/278] text corrections --- client/translations/amneziavpn_ru.ts | 38 +++++++++++++------ client/translations/amneziavpn_zh_CN.ts | 38 +++++++++++++------ .../ui/qml/Pages2/PageSettingsApplication.qml | 2 +- 3 files changed, 53 insertions(+), 25 deletions(-) diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index 7bb59a7e..8d9e5e92 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -984,9 +984,13 @@ Already installed containers were found on the server. All installed containers Авто-запуск - Launch the application every time %1 starts - Запускать приложение при каждом включении %1 + Запускать приложение при каждом включении %1 + + + + Launch the application every time the device is starts + Запускать приложение при каждом включении устройства @@ -1184,52 +1188,52 @@ Already installed containers were found on the server. All installed containers DNS сервер - + If AmneziaDNS is not used or installed Эти адреса будут использоваться, если не включен или не установлен AmneziaDNS - + Primary DNS Первичный DNS - + Secondary DNS Вторичный DNS - + Restore default Восстановить по умолчанию - + Restore default DNS settings? Восстановить настройки DNS по умолчанию? - + Continue Продолжить - + Cancel Отменить - + Settings have been reset Настройки сброшены - + Save Сохранить - + Settings saved Сохранить настройки @@ -2638,6 +2642,16 @@ This means that AmneziaWG keeps the fast performance of the original while addin error 0x%1: %2 error 0x%1: %2 + + + WireGuard Configuration Highlighter + + + + + &Randomize colors + + SelectLanguageDrawer diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index 576112de..60e03307 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -1046,9 +1046,13 @@ And if you don't like the app, all the more support it - the donation will 启动时自动运行运用程序 - Launch the application every time %1 starts - 运行应用软件在%1系统启动时 + 运行应用软件在%1系统启动时 + + + + Launch the application every time the device is starts + @@ -1266,52 +1270,52 @@ And if you don't like the app, all the more support it - the donation will DNS服务器 - + If AmneziaDNS is not used or installed 如果未使用或未安装AmneziaDNS - + Primary DNS 首选 DNS - + Secondary DNS 备用 DNS - + Restore default 恢复默认配置 - + Restore default DNS settings? 是否恢复默认DNS配置? - + Continue 继续 - + Cancel 取消 - + Settings have been reset 已重置 - + Save 保存 - + Settings saved 配置已保存 @@ -2771,6 +2775,16 @@ While it offers a blend of security, stability, and speed, it's essential t error 0x%1: %2 错误 0x%1: %2 + + + WireGuard Configuration Highlighter + + + + + &Randomize colors + + SelectLanguageDrawer diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index 49e3a5d9..05e468f0 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -70,7 +70,7 @@ PageType { Layout.margins: 16 text: qsTr("Auto start") - descriptionText: qsTr("Launch the application every time %1 starts").arg(Qt.platform.os) + descriptionText: qsTr("Launch the application every time the device is starts") checked: SettingsController.isAutoStartEnabled() onCheckedChanged: { From 9cf5590371876be5f084fbbc1622f60455f803a3 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 16 Oct 2023 15:17:09 +0500 Subject: [PATCH 240/278] disabled split site tunneling for awg --- client/amnezia_application.cpp | 15 +++++++++------ client/translations/amneziavpn_ru.ts | 8 ++++++-- client/translations/amneziavpn_zh_CN.ts | 8 ++++++-- client/ui/models/sites_model.cpp | 1 + client/ui/qml/Pages2/PageSettingsConnection.qml | 2 +- 5 files changed, 23 insertions(+), 11 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 904ffaa6..da00dae7 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -139,7 +139,8 @@ void AmneziaApplication::init() &ConnectionController::openConnection); connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(), &ConnectionController::closeConnection); - connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated); + connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(), + &NotificationHandler::onTranslationsUpdated); m_engine->load(url); m_systemController->setQmlRoot(m_engine->rootObjects().value(0)); @@ -226,14 +227,13 @@ void AmneziaApplication::loadTranslator() updateTranslator(locale); } - void AmneziaApplication::updateTranslator(const QLocale &locale) { if (!m_translator->isEmpty()) { QCoreApplication::removeTranslator(m_translator.get()); } - QString strFileName = QString(":/translations/amneziavpn")+QLatin1String("_")+locale.name()+".qm"; + QString strFileName = QString(":/translations/amneziavpn") + QLatin1String("_") + locale.name() + ".qm"; if (m_translator->load(strFileName)) { if (QCoreApplication::installTranslator(m_translator.get())) { m_settings->setAppLanguage(locale); @@ -295,11 +295,13 @@ void AmneziaApplication::initModels() m_sitesModel.reset(new SitesModel(m_settings, this)); m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get()); connect(m_containersModel.get(), &ContainersModel::defaultContainerChanged, this, [this]() { - if (m_containersModel->getDefaultContainer() == DockerContainer::WireGuard + if ((m_containersModel->getDefaultContainer() == DockerContainer::WireGuard + || m_containersModel->getDefaultContainer() == DockerContainer::Awg) && m_sitesModel->isSplitTunnelingEnabled()) { m_sitesModel->toggleSplitTunneling(false); emit m_pageController->showNotificationMessage( - tr("Split tunneling for WireGuard is not implemented, the option was disabled")); + tr("Split tunneling for %1 is not implemented, the option was disabled") + .arg(ContainerProps::containerHumanNames().value(m_containersModel->getDefaultContainer()))); } }); @@ -335,7 +337,8 @@ void AmneziaApplication::initControllers() m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel, m_vpnConnection)); m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get()); - connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(), &ConnectionController::onTranslationsUpdated); + connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(), + &ConnectionController::onTranslationsUpdated); m_pageController.reset(new PageController(m_serversModel, m_settings)); m_engine->rootContext()->setContextProperty("PageController", m_pageController.get()); diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index de026ce1..acb7ac09 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -4,9 +4,13 @@ AmneziaApplication - Split tunneling for WireGuard is not implemented, the option was disabled - Раздельное туннелирование для "Wireguard" не реализовано,опция отключена + Раздельное туннелирование для "Wireguard" не реализовано,опция отключена + + + + Split tunneling for %1 is not implemented, the option was disabled + Раздельное туннелирование для %1 не реализовано, опция отключена diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index 77879a50..658a2c15 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -4,9 +4,13 @@ AmneziaApplication - Split tunneling for WireGuard is not implemented, the option was disabled - 未启用选项,还未实现基于WireGuard协议的VPN分离 + 未启用选项,还未实现基于WireGuard协议的VPN分离 + + + + Split tunneling for %1 is not implemented, the option was disabled + diff --git a/client/ui/models/sites_model.cpp b/client/ui/models/sites_model.cpp index 1e0f1692..61748bfc 100644 --- a/client/ui/models/sites_model.cpp +++ b/client/ui/models/sites_model.cpp @@ -111,6 +111,7 @@ void SitesModel::toggleSplitTunneling(bool enabled) } else { m_settings->setRouteMode(Settings::RouteMode::VpnAllSites); } + m_isSplitTunnelingEnabled = enabled; } QVector > SitesModel::getCurrentSites() diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml index b5b1bd97..f6ecafd7 100644 --- a/client/ui/qml/Pages2/PageSettingsConnection.qml +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -125,7 +125,7 @@ PageType { } DividerType { - visible: !GC.isMobile() + visible: false//!GC.isMobile() } } } From 7cc0f39d3ca12fd05fde3d7b6c06030d66e01c55 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Mon, 16 Oct 2023 22:21:01 +0800 Subject: [PATCH 241/278] adapted pagehome by new custom drawer type --- client/resources.qrc | 1 + .../qml/Components/HomeContainersListView.qml | 2 +- .../ui/qml/Components/HomeRootMenuButton.qml | 140 ++++++++++ client/ui/qml/Controls2/Drawer2Type.qml | 68 +---- client/ui/qml/Controls2/DropDownType.qml | 9 +- client/ui/qml/Pages2/PageHome.qml | 242 +++--------------- 6 files changed, 194 insertions(+), 268 deletions(-) create mode 100644 client/ui/qml/Components/HomeRootMenuButton.qml diff --git a/client/resources.qrc b/client/resources.qrc index d7f8ff7a..ac6e5a6d 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -217,5 +217,6 @@ ui/qml/Controls2/TopCloseButtonType.qml images/controls/x-circle.svg ui/qml/Controls2/Drawer2Type.qml + ui/qml/Components/HomeRootMenuButton.qml diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index 86a755c1..1436c747 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -69,7 +69,7 @@ ListView { isDefault = true menuContent.currentIndex = index - containersDropDown.menuVisible = false + containersDropDown.menu.state = "closed" if (needReconnected && diff --git a/client/ui/qml/Components/HomeRootMenuButton.qml b/client/ui/qml/Components/HomeRootMenuButton.qml new file mode 100644 index 00000000..b2ca98dc --- /dev/null +++ b/client/ui/qml/Components/HomeRootMenuButton.qml @@ -0,0 +1,140 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import "../Controls2/TextTypes" +import "../Controls2" + +Item { + id: root + + property string text + property int textMaximumLineCount: 2 + property int textElide: Qt.ElideRight + + property string descriptionText + + property var clickedFunction + + property string rightImageSource + + property string textColor: "#d7d8db" + property string descriptionColor: "#878B91" + property real textOpacity: 1.0 + + property string rightImageColor: "#d7d8db" + + property bool descriptionOnTop: false + + property string defaultServerHostName + property string defaultContainerName + + implicitWidth: content.implicitWidth + content.anchors.topMargin + content.anchors.bottomMargin + implicitHeight: content.implicitHeight + content.anchors.leftMargin + content.anchors.rightMargin + + ColumnLayout { + id: content + + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + + RowLayout { + Layout.topMargin: 24 + Layout.leftMargin: 24 + Layout.rightMargin: 24 + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + + Header1TextType { + Layout.maximumWidth: root.width - 48 - 18 - 12 // todo + + maximumLineCount: 2 + elide: Qt.ElideRight + + text: root.text + Layout.alignment: Qt.AlignLeft + } + + + ImageButtonType { + id: rightImage + + hoverEnabled: false + image: rightImageSource + imageColor: rightImageColor + visible: rightImageSource ? true : false + +// implicitSize: 18 +// backGroudRadius: 5 + horizontalPadding: 0 + padding: 0 + spacing: 0 + + + Rectangle { + id: rightImageBackground + anchors.fill: parent + radius: 16 + color: "transparent" + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + } + } + + LabelTextType { + Layout.bottomMargin: 44 + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + + text: { + var description = "" + if (ServersModel.isDefaultServerHasWriteAccess()) { + if (SettingsController.isAmneziaDnsEnabled() + && ContainersModel.isAmneziaDnsContainerInstalled(ServersModel.getDefaultServerIndex())) { + description += "Amnezia DNS | " + } + } else { + if (ServersModel.isDefaultServerConfigContainsAmneziaDns()) { + description += "Amnezia DNS | " + } + } + + description += root.defaultContainerName + " | " + root.defaultServerHostName + return description + } + } + } + + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + + onEntered: { + rightImageBackground.color = rightImage.hoveredColor + + root.textOpacity = 0.8 + } + + onExited: { + rightImageBackground.color = rightImage.defaultColor + + root.textOpacity = 1 + } + + onPressedChanged: { + rightImageBackground.color = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor + + root.textOpacity = 0.7 + } + + onClicked: { + if (clickedFunction && typeof clickedFunction === "function") { + clickedFunction() + } + } + } +} diff --git a/client/ui/qml/Controls2/Drawer2Type.qml b/client/ui/qml/Controls2/Drawer2Type.qml index 2daae742..3f3cf54b 100644 --- a/client/ui/qml/Controls2/Drawer2Type.qml +++ b/client/ui/qml/Controls2/Drawer2Type.qml @@ -22,10 +22,9 @@ Item { property string defaultColor: "#1C1D21" property string borderColor: "#2C2D30" - property int collapsedHeight: 0 property bool needCollapsed: false - property double scaely + property int contentHeight: 0 property Item contentParent: contentArea @@ -50,15 +49,10 @@ Item { MouseArea { id: fullArea anchors.fill: parent - enabled: (root.state === "expanded" || root.state === "opened") + enabled: (root.state === "opened") hoverEnabled: true onClicked: { - if (root.state === "expanded") { - root.state = "collapsed" - return - } - if (root.state === "opened") { root.state = "closed" return @@ -101,7 +95,7 @@ Item { anchors.fill: parent - cursorShape: (root.state === "opened" || root.state === "expanded") ? Qt.PointingHandCursor : Qt.ArrowCursor + cursorShape: (root.state === "opened") ? Qt.PointingHandCursor : Qt.ArrowCursor hoverEnabled: true drag.target: root @@ -113,35 +107,17 @@ Item { onReleased: { if (root.state === "closed" && root.y < dragArea.drag.maximumY) { root.state = "opened" - // PageController.drawerOpen() return } if (root.state === "opened" && root.y > dragArea.drag.minimumY) { root.state = "closed" - // PageController.drawerClose() - return - } - - if (root.state === "collapsed" && root.y < dragArea.drag.maximumY) { - root.state = "expanded" - return - } - if (root.state === "expanded" && root.y > dragArea.drag.minimumY) { - root.state = "collapsed" return } } onClicked: { - if (root.state === "expanded") { - // PageController.drawerOpen() - root.state = "collapsed" - return - } - if (root.state === "opened") { - // PageController.drawerClose() root.state = "closed" return } @@ -152,7 +128,7 @@ Item { } onStateChanged: { - if (root.state === "closed" || root.state === "collapsed") { + if (root.state === "closed") { var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor() if (initialPageNavigationBarColor !== 0xFF1C1D21) { PageController.updateNavigationBarColor(initialPageNavigationBarColor) @@ -166,7 +142,7 @@ Item { return } - if (root.state === "expanded" || root.state === "opened") { + if (root.state === "opened") { if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) { PageController.updateNavigationBarColor(0xFF1C1D21) } @@ -181,21 +157,6 @@ Item { /** Two states of buttonContent, great place to add any future animations for the drawer */ states: [ - State { - name: "collapsed" - PropertyChanges { - target: root - y: root.height - collapsedHeight - } - }, - State { - name: "expanded" - PropertyChanges { - target: root - y: dragArea.drag.minimumY - } - }, - State { name: "closed" PropertyChanges { @@ -214,25 +175,6 @@ Item { ] transitions: [ - Transition { - from: "collapsed" - to: "expanded" - PropertyAnimation { - target: root - properties: "y" - duration: 200 - } - }, - Transition { - from: "expanded" - to: "collapsed" - PropertyAnimation { - target: root - properties: "y" - duration: 200 - } - }, - Transition { from: "opened" to: "closed" diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 83518bef..0506bdc7 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -42,6 +42,8 @@ Item { property Item drawerParent: root + property Drawer2Type menu: menu + implicitWidth: rootButtonContent.implicitWidth implicitHeight: rootButtonContent.implicitHeight @@ -157,13 +159,12 @@ Item { onClicked: { if (rootButtonClickedFunction && typeof rootButtonClickedFunction === "function") { rootButtonClickedFunction() - } else { - menu.open() } + + menu.open() } } - //DrawerType { Drawer2Type { id: menu @@ -186,7 +187,7 @@ Item { BackButtonType { backButtonImage: root.headerBackButtonImage backButtonFunction: function() { - root.menuVisible = false + menu.state = "closed" } } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 519c17a5..dafa3bdc 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -30,13 +30,13 @@ PageType { target: PageController function onRestorePageHomeState(isContainerInstalled) { - buttonContent.state = "expanded" + menu.close() if (isContainerInstalled) { containersDropDown.menuVisible = true } } function onForceCloseDrawer() { - buttonContent.state = "collapsed" + menu.close() } } @@ -69,79 +69,27 @@ PageType { } } - collapsedServerMenuDescription.text = description + root.defaultContainerName + " | " + root.defaultServerHostName + // collapsedServerMenuDescription.text = description + root.defaultContainerName + " | " + root.defaultServerHostName expandedServersMenuDescription.text = description + root.defaultServerHostName } - Component.onCompleted: updateDescriptions() - - MouseArea { - anchors.fill: parent - enabled: buttonContent.state === "expanded" - onClicked: { - buttonContent.state = "collapsed" - } + Component.onCompleted: { + updateDescriptions() } Item { anchors.fill: parent - anchors.bottomMargin: buttonContent.collapsedHeight + anchors.bottomMargin: defaultServerInfo.implicitHeight ConnectButton { anchors.centerIn: parent } } - MouseArea { - id: dragArea - - anchors.fill: buttonBackground - cursorShape: buttonContent.state === "collapsed" ? Qt.PointingHandCursor : Qt.ArrowCursor - hoverEnabled: true - - drag.target: buttonContent - drag.axis: Drag.YAxis - drag.maximumY: root.height - buttonContent.collapsedHeight - drag.minimumY: root.height - root.height * 0.9 - - /** If drag area is released at any point other than min or max y, transition to the other state */ - onReleased: { - if (buttonContent.state === "collapsed" && buttonContent.y < dragArea.drag.maximumY) { - buttonContent.state = "expanded" - return - } - if (buttonContent.state === "expanded" && buttonContent.y > dragArea.drag.minimumY) { - buttonContent.state = "collapsed" - return - } - } - - onEntered: { - collapsedButtonChevron.backgroundColor = collapsedButtonChevron.hoveredColor - collapsedButtonHeader.opacity = 0.8 - } - onExited: { - collapsedButtonChevron.backgroundColor = collapsedButtonChevron.defaultColor - collapsedButtonHeader.opacity = 1 - } - onPressedChanged: { - collapsedButtonChevron.backgroundColor = pressed ? collapsedButtonChevron.pressedColor : entered ? collapsedButtonChevron.hoveredColor : collapsedButtonChevron.defaultColor - collapsedButtonHeader.opacity = 0.7 - } - - - onClicked: { - if (buttonContent.state === "collapsed") { - buttonContent.state = "expanded" - } - } - } - Rectangle { id: buttonBackground + anchors.fill: defaultServerInfo - anchors { left: buttonContent.left; right: buttonContent.right; top: buttonContent.top } - height: root.height radius: 16 color: root.defaultColor border.color: root.borderColor @@ -157,151 +105,40 @@ PageType { } } - ColumnLayout { - id: buttonContent + HomeRootMenuButton { + id: defaultServerInfo + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom - /** Initial height of button content */ - property int collapsedHeight: 0 - /** True when expanded objects should be visible */ - property bool expandedVisibility: buttonContent.state === "expanded" || (buttonContent.state === "collapsed" && dragArea.drag.active === true) - /** True when collapsed objects should be visible */ - property bool collapsedVisibility: buttonContent.state === "collapsed" && dragArea.drag.active === false + text: root.defaultServerName + rightImageSource: "qrc:/images/controls/chevron-down.svg" - Drag.active: dragArea.drag.active - anchors.right: root.right - anchors.left: root.left - y: root.height - buttonContent.height + defaultContainerName: root.defaultContainerName + defaultServerHostName: root.defaultServerHostName - Component.onCompleted: { - buttonContent.state = "collapsed" + clickedFunction: function() { + menu.open() } + } - /** Set once based on first implicit height change once all children are layed out */ - onImplicitHeightChanged: { - if (buttonContent.state === "collapsed" && collapsedHeight == 0) { - collapsedHeight = implicitHeight - } - } + Drawer2Type { + id: menu + parent: root - onStateChanged: { - if (buttonContent.state === "collapsed") { - var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor() - if (initialPageNavigationBarColor !== 0xFF1C1D21) { - PageController.updateNavigationBarColor(initialPageNavigationBarColor) - } - PageController.drawerClose() - return - } - if (buttonContent.state === "expanded") { - if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) { - PageController.updateNavigationBarColor(0xFF1C1D21) - } - PageController.drawerOpen() - return - } - } + width: parent.width + height: parent.height + contentHeight: parent.height * 0.9 - /** Two states of buttonContent, great place to add any future animations for the drawer */ - states: [ - State { - name: "collapsed" - PropertyChanges { - target: buttonContent - y: root.height - collapsedHeight - } - }, - State { - name: "expanded" - PropertyChanges { - target: buttonContent - y: dragArea.drag.minimumY - - } - } - ] - - transitions: [ - Transition { - from: "collapsed" - to: "expanded" - PropertyAnimation { - target: buttonContent - properties: "y" - duration: 200 - } - }, - Transition { - from: "expanded" - to: "collapsed" - PropertyAnimation { - target: buttonContent - properties: "y" - duration: 200 - } - } - ] - - RowLayout { - Layout.topMargin: 24 - Layout.leftMargin: 24 - Layout.rightMargin: 24 - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - visible: buttonContent.collapsedVisibility - - spacing: 0 - - Header1TextType { - id: collapsedButtonHeader - Layout.maximumWidth: buttonContent.width - 48 - 18 - 12 // todo - - maximumLineCount: 2 - elide: Qt.ElideRight - - text: root.defaultServerName - horizontalAlignment: Qt.AlignHCenter - - Behavior on opacity { - PropertyAnimation { duration: 200 } - } - } - - ImageButtonType { - id: collapsedButtonChevron - - Layout.leftMargin: 8 - - hoverEnabled: false - image: "qrc:/images/controls/chevron-down.svg" - imageColor: "#d7d8db" - - icon.width: 18 - icon.height: 18 - backgroundRadius: 16 - horizontalPadding: 4 - topPadding: 4 - bottomPadding: 3 - - onClicked: { - if (buttonContent.state === "collapsed") { - buttonContent.state = "expanded" - } - } - } - } - - LabelTextType { - id: collapsedServerMenuDescription - Layout.bottomMargin: 44 - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - visible: buttonContent.collapsedVisibility - } ColumnLayout { id: serversMenuHeader - Layout.alignment: Qt.AlignTop | Qt.AlignHCenter - Layout.fillWidth: true - visible: buttonContent.expandedVisibility + parent: menu.contentParent + + anchors.top: parent.top + anchors.right: parent.right + anchors.left: parent.left Header1TextType { Layout.fillWidth: true @@ -330,6 +167,8 @@ PageType { DropDownType { id: containersDropDown + drawerParent: root + rootButtonImageColor: "#0E0E11" rootButtonBackgroundColor: "#D7D8DB" rootButtonBackgroundHoveredColor: Qt.rgba(215, 216, 219, 0.8) @@ -383,7 +222,6 @@ PageType { Layout.topMargin: 48 Layout.leftMargin: 16 Layout.rightMargin: 16 - visible: buttonContent.expandedVisibility headerText: qsTr("Servers") } @@ -391,12 +229,16 @@ PageType { Flickable { id: serversContainer - Layout.alignment: Qt.AlignTop | Qt.AlignHCenter - Layout.fillWidth: true - Layout.topMargin: 16 + + parent: menu.contentParent + + anchors.top: serversMenuHeader.bottom + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + anchors.topMargin: 16 contentHeight: col.implicitHeight - implicitHeight: root.height - (root.height * 0.1) - serversMenuHeader.implicitHeight - 52 //todo 52 is tabbar height - visible: buttonContent.expandedVisibility + clip: true ScrollBar.vertical: ScrollBar { @@ -500,7 +342,7 @@ PageType { onClicked: function() { ServersModel.currentlyProcessedIndex = index PageController.goToPage(PageEnum.PageSettingsServerInfo) - buttonContent.state = "collapsed" + menu.close() } } } From 36ba3758db6b3f16e4c5dc11e8e8f581177a33ca Mon Sep 17 00:00:00 2001 From: pokamest Date: Mon, 16 Oct 2023 15:27:26 +0100 Subject: [PATCH 242/278] Translation updates --- client/translations/amneziavpn_ru.ts | 101 ++++++++++++++++-------- client/translations/amneziavpn_zh_CN.ts | 10 --- 2 files changed, 70 insertions(+), 41 deletions(-) diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index 8d9e5e92..857d390f 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -611,7 +611,7 @@ Already installed containers were found on the server. All installed containers All users who you shared a connection with will no longer be able to connect to it. - Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. + Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. @@ -765,7 +765,7 @@ Already installed containers were found on the server. All installed containers Remove SFTP and all data stored there? - Удалить SFTP-хранилище и все хранящиеся на нем данные? + Удалить SFTP-хранилище и все хранящиеся на нем данные? @@ -983,14 +983,10 @@ Already installed containers were found on the server. All installed containers Auto start Авто-запуск - - Launch the application every time %1 starts - Запускать приложение при каждом включении %1 - Launch the application every time the device is starts - Запускать приложение при каждом включении устройства + Запускать приложение при каждом включении @@ -1089,7 +1085,7 @@ Already installed containers were found on the server. All installed containers Backup file saved - + Бэкап файл сохранен @@ -1248,7 +1244,7 @@ Already installed containers were found on the server. All installed containers Save logs - Сохранить логи + Сохранять логи @@ -1268,7 +1264,7 @@ Already installed containers were found on the server. All installed containers Logs file saved - + Файл с логами сохранен @@ -1704,12 +1700,12 @@ and will not be shared or disclosed to the Amnezia or any third parties Amnezia has detected that your server is currently - + Amnezia обнаружила, что ваш сервер в настоящее время busy installing other software. Amnezia installation - + занят установкой другого программного обеспечения. Установка Amnezia @@ -2428,7 +2424,15 @@ While it offers a blend of security, stability, and speed, it's essential t * Minimal configuration * Recognised by DPI analysis systems * Works over UDP network protocol, ports 500 and 4500. - + IKEv2 в сочетании с уровнем шифрования IPSec это современный и стабильный протокол VPN. +Он может быстро переключаться между сетями и устройствами, что делает его особенно адаптивным в динамичных сетевых средах. +Несмотря на сочетание безопасности, стабильности и скорости, необходимо отметить, что IKEv2 легко обнаруживается и подвержен блокировке. + +* Доступно в AmneziaVPN только для Windows. +* Низкое энергопотребление, на мобильных устройствах +* Минимальная конфигурация +* Распознается системами DPI-анализа +* Работает по сетевому протоколу UDP, порты 500 и 4500. @@ -2474,7 +2478,7 @@ While it offers a blend of security, stability, and speed, it's essential t AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, but very resistant to blockages. Recommended for regions with high levels of censorship. - + AmneziaWG - Специальный протокол от Amnezia, основанный на протоколе WireGuard. Он такой же быстрый, как WireGuard, но очень устойчив к блокировкам. Рекомендуется для регионов с высоким уровнем цензуры. @@ -2506,7 +2510,14 @@ It employs its unique security protocol, leveraging the strength of SSL/TLS for * Flexible customisation to suit user needs to work with different operating systems and devices * Recognised by DPI analysis systems and therefore susceptible to blocking * Can operate over both TCP and UDP network protocols. - + OpenVPN однин из самых популярных и проверенных временем VPN-протоколов. +В нем используется уникальный протокол безопасности, опирающийся на протокол SSL/TLS для шифрования и обмена ключами. Кроме того, поддержка OpenVPN множества методов аутентификации делает его универсальным и адаптируемым к широкому спектру устройств и операционных систем. Благодаря открытому исходному коду OpenVPN подвергается тщательному анализу со стороны мирового сообщества, что постоянно повышает его безопасность. Благодаря оптимальному соотношению производительности, безопасности и совместимости OpenVPN остается лучшим выбором как для частных лиц, так и для компаний, заботящихся о конфиденциальности. + +* Доступность AmneziaVPN для всех платформ +* Нормальное энергопотребление на мобильных устройствах +* Гибкая настройка под нужды пользователя для работы с различными операционными системами и устройствами +* Распознается системами DPI-анализа и поэтому подвержен блокировке +* Может работать по сетевым протоколам TCP и UDP. @@ -2518,7 +2529,12 @@ It employs its unique security protocol, leveraging the strength of SSL/TLS for * Configurable encryption protocol * Detectable by some DPI systems * Works over TCP network protocol. - + Shadowsocks, создан на основе протокола SOCKS5, защищает соединение с помощью шифра AEAD. Несмотря на то, что протокол Shadowsocks разработан таким образом, чтобы быть незаметным и сложным для идентификации, он не идентичен стандартному HTTPS-соединению. Однако некоторые системы анализа трафика все же могут обнаружить соединение Shadowsocks. В связи с ограниченной поддержкой в Amnezia рекомендуется использовать протокол AmneziaWG, или OpenVPN over Cloak. + +* Доступен в AmneziaVPN только на ПК ноутбуках. +* Настраиваемый протокол шифрования +* Обнаруживается некоторыми DPI-системами +* Работает по сетевому протоколу TCP. @@ -2540,7 +2556,24 @@ If there is a extreme level of Internet censorship in your region, we advise you * Not recognised by DPI analysis systems * Works over TCP network protocol, 443 port. - + OpenVPN over Cloak - это комбинация протокола OpenVPN и плагина Cloak, разработанного специально для защиты от блокировок. + +OpenVPN обеспечивает безопасное VPN-соединение за счет шифрования всего интернет-трафика между клиентом и сервером. + +Cloak защищает OpenVPN от обнаружения и блокировок. + +Cloak может изменять метаданные пакетов. Он полностью маскирует VPN-трафик под обычный веб-трафик, а также защищает VPN от обнаружения с помощью Active Probing. Это делает ее очень устойчивой к обнаружению + +Сразу же после получения первого пакета данных Cloak проверяет подлинность входящего соединения. Если аутентификация не проходит, плагин маскирует сервер под поддельный сайт, и ваша VPN становится невидимой для аналитических систем. + +Если в вашем регионе существует экстремальный уровень цензуры в Интернете, мы советуем вам при первом подключении использовать только OpenVPN через Cloak + +* Доступность AmneziaVPN на всех платформах +* Высокое энергопотребление на мобильных устройствах +* Гибкие настройки +* Не распознается системами DPI-анализа +* Работает по сетевому протоколу TCP, 443 порт. + @@ -2553,7 +2586,15 @@ WireGuard is very susceptible to blocking due to its distinct packet signatures. * Minimum number of settings * Easily recognised by DPI analysis systems, susceptible to blocking * Works over UDP network protocol. - + WireGuard - относительно новый популярный VPN-протокол с упрощенной архитектурой. +Обеспечивает стабильное VPN-соединение, высокую производительность на всех устройствах. Использует жестко заданные настройки шифрования. WireGuard по сравнению с OpenVPN имеет меньшую задержку и лучшую пропускную способность при передаче данных. +WireGuard очень восприимчив к блокированию из-за особенностей сигнатур пакетов. В отличие от некоторых других VPN-протоколов, использующих методы обфускации, последовательные сигнатуры пакетов WireGuard легче выявляются и, соответственно, блокируются современными системами глубокой проверки пакетов (DPI) и другими средствами сетевого мониторинга. + +* Доступность AmneziaVPN для всех платформ +* Низкое энергопотребление +* Минимальное количество настроек +* Легко распознается системами DPI-анализа, подвержен блокировке +* Работает по сетевому протоколу UDP. @@ -2566,7 +2607,15 @@ This means that AmneziaWG keeps the fast performance of the original while addin * Minimum number of settings * Not recognised by DPI analysis systems, resistant to blocking * Works over UDP network protocol. - + AmneziaWG - усовершенствованная версия популярного VPN-протокола Wireguard. AmneziaWG опирается на фундамент, заложенный WireGuard, сохраняя упрощенную архитектуру и высокопроизводительные возможности работы на разных устройствах. +Хотя WireGuard известен своей эффективностью, у него были проблемы с обнаружением из-за характерных сигнатур пакетов. AmneziaWG решает эту проблему за счет использования более совершенных методов обфускации, благодаря чему его трафик сливается с обычным интернет-трафиком. +Таким образом, AmneziaWG сохраняет высокую производительность оригинала, добавляя при этом дополнительный уровень скрытности, что делает его отличным выбором для тех, кому нужно быстрое и незаметное VPN-соединение. + +* Доступность AmneziaVPN на всех платформах +* Низкое энергопотребление +* Минимальное количество настроек +* Не распознается системами DPI-анализа, устойчив к блокировке +* Работает по сетевому протоколу UDP. AmneziaWG container @@ -2642,16 +2691,6 @@ This means that AmneziaWG keeps the fast performance of the original while addin error 0x%1: %2 error 0x%1: %2 - - - WireGuard Configuration Highlighter - - - - - &Randomize colors - - SelectLanguageDrawer @@ -2877,7 +2916,7 @@ This means that AmneziaWG keeps the fast performance of the original while addin Medium or High - Спедний или Высокий + Средний или Высокий @@ -2925,7 +2964,7 @@ This means that AmneziaWG keeps the fast performance of the original while addin Private key passphrase - Кодовая фраза для закрытого ключа + Кодовая фраза для закрытого ключа diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index 60e03307..b908bf56 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -2775,16 +2775,6 @@ While it offers a blend of security, stability, and speed, it's essential t error 0x%1: %2 错误 0x%1: %2 - - - WireGuard Configuration Highlighter - - - - - &Randomize colors - - SelectLanguageDrawer From 9eb23e38bd9b31cc50794312bd7cbd8793fdce3a Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 16 Oct 2023 22:57:12 +0500 Subject: [PATCH 243/278] disabled the ability to change the protocol/server when a vpn connection is active --- client/translations/amneziavpn_ru.ts | 13 ++++++-- client/translations/amneziavpn_zh_CN.ts | 13 ++++++-- .../qml/Components/HomeContainersListView.qml | 30 +++++++------------ client/ui/qml/Controls2/CardType.qml | 2 ++ client/ui/qml/Pages2/PageHome.qml | 6 ++++ 5 files changed, 41 insertions(+), 23 deletions(-) diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index 8d9e5e92..ad2ebfbe 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -119,13 +119,17 @@ HomeContainersListView + Unable change protocol while there is an active connection + + + + The selected protocol is not supported on the current platform Выбранный протокол не поддерживается на данном устройстве - Reconnect via VPN Procotol: - Переподключение через VPN протокол: + Переподключение через VPN протокол: @@ -267,6 +271,11 @@ Already installed containers were found on the server. All installed containers Servers Серверы + + + Unable change server while there is an active connection + + PageProtocolAwgSettings diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index 60e03307..0def921b 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -130,13 +130,17 @@ HomeContainersListView + Unable change protocol while there is an active connection + Невозможно изменить протокол при наличии активного соединения + + + The selected protocol is not supported on the current platform 当前平台不支持所选协议 - Reconnect via VPN Procotol: - 重连VPN基于协议: + 重连VPN基于协议: @@ -301,6 +305,11 @@ Already installed containers were found on the server. All installed containers Servers 服务器 + + + Unable change server while there is an active connection + Невозможно изменить сервер при наличии активного соединения + PageProtocolAwgSettings diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index 2304427f..f05b90d6 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -50,34 +50,26 @@ ListView { imageSource: "qrc:/images/controls/download.svg" showImage: !isInstalled - checkable: isInstalled + checkable: isInstalled && !ConnectionController.isConnected && isSupported checked: isDefault - onPressed: function(mouse) { - if (!isSupported) { - PageController.showErrorMessage(qsTr("The selected protocol is not supported on the current platform")) - } - } - onClicked: { - if (checked) { - var needReconnected = false - if (!isDefault) { - needReconnected = true - } + if (ConnectionController.isConnected && isInstalled) { + PageController.showNotificationMessage(qsTr("Unable change protocol while there is an active connection")) + return + } + if (checked) { isDefault = true menuContent.currentIndex = index containersDropDown.menuVisible = false - - - if (needReconnected && (ConnectionController.isConnected || ConnectionController.isConnectionInProgress)) { - PageController.showNotificationMessage(qsTr("Reconnect via VPN Procotol: ") + name) - PageController.goToPageHome() - ConnectionController.openConnection() - } } else { + if (!isSupported && isInstalled) { + PageController.showErrorMessage(qsTr("The selected protocol is not supported on the current platform")) + return + } + ContainersModel.setCurrentlyProcessedContainerIndex(proxyContainersModel.mapToSource(index)) InstallController.setShouldCreateServer(false) PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings) diff --git a/client/ui/qml/Controls2/CardType.qml b/client/ui/qml/Controls2/CardType.qml index 8429acb8..32f89122 100644 --- a/client/ui/qml/Controls2/CardType.qml +++ b/client/ui/qml/Controls2/CardType.qml @@ -81,6 +81,7 @@ RadioButton { Text { text: root.headerText + wrapMode: Text.WordWrap color: "#D7D8DB" font.pixelSize: 25 font.weight: 700 @@ -110,6 +111,7 @@ RadioButton { Text { text: root.footerText + wrapMode: Text.WordWrap visible: root.footerText !== "" color: "#878B91" font.pixelSize: 13 diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 519c17a5..d89d6be1 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -471,10 +471,16 @@ PageType { } checked: index === serversMenuContent.currentIndex + checkable: !ConnectionController.isConnected ButtonGroup.group: serversRadioButtonGroup onClicked: { + if (ConnectionController.isConnected) { + PageController.showNotificationMessage(qsTr("Unable change server while there is an active connection")) + return + } + serversMenuContent.currentIndex = index ServersModel.currentlyProcessedIndex = index From 5369e68267828ce243e32b1aca70a0de218e34f1 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Tue, 17 Oct 2023 14:30:59 +0800 Subject: [PATCH 244/278] updated Chinese translations for updating source strings --- client/translations/amneziavpn_zh_CN.ts | 105 ++++++++++++++++++------ 1 file changed, 80 insertions(+), 25 deletions(-) diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index b908bf56..e1f8a13a 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -74,17 +74,17 @@ Add new connection - + 添加新连接 Configure your server - + 配置您的服务器 Open config file, key or QR code - + 配置文件,授权码或二维码 Server IP, login and password @@ -860,7 +860,7 @@ Already installed containers were found on the server. All installed containers When configuring WordPress set the this onion address as domain. - + 配置 WordPress 时,将此洋葱地址设置为域。 When configuring WordPress set the domain as this onion address. @@ -1052,7 +1052,7 @@ And if you don't like the app, all the more support it - the donation will Launch the application every time the device is starts - + 每次设备启动时启动应用程序 @@ -1155,7 +1155,7 @@ And if you don't like the app, all the more support it - the donation will Backup file saved - + 备份文件已保存 @@ -1350,7 +1350,7 @@ And if you don't like the app, all the more support it - the donation will Logs file saved - + 日志文件已保存 @@ -1705,7 +1705,7 @@ It's okay as long as it's from someone you trust. Configure your server - + 配置服务器 @@ -1720,7 +1720,7 @@ It's okay as long as it's from someone you trust. Login to connect via SSH - ssh账号 + 用户 @@ -1736,7 +1736,8 @@ It's okay as long as it's from someone you trust. All data you enter will remain strictly confidential and will not be shared or disclosed to the Amnezia or any third parties - + 您输入的所有数据将严格保密 +不会向 Amnezia 或任何第三方分享或披露 @@ -1803,12 +1804,12 @@ and will not be shared or disclosed to the Amnezia or any third parties Amnezia has detected that your server is currently - + Amnezia 检测到您的服务器当前 busy installing other software. Amnezia installation - + 正安装其他软件。Amnezia安装 Amnesia has detected that your server is currently @@ -2017,7 +2018,7 @@ and will not be shared or disclosed to the Amnezia or any third parties Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the server, as well as change settings. - + 除访问VPN外,用户还能添加和删除协议、服务以及更改配置信息 Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the servers, as well as change settings. @@ -2578,7 +2579,7 @@ and will not be shared or disclosed to the Amnezia or any third parties AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, but very resistant to blockages. Recommended for regions with high levels of censorship. - + AmneziaWG - Amnezia 的特殊协议,基于 WireGuard。它的速度像 WireGuard 一样快,但非常抗堵塞。推荐用于审查较严的地区。 @@ -2610,7 +2611,14 @@ It employs its unique security protocol, leveraging the strength of SSL/TLS for * Flexible customisation to suit user needs to work with different operating systems and devices * Recognised by DPI analysis systems and therefore susceptible to blocking * Can operate over both TCP and UDP network protocols. - + OpenVPN 是最流行且经过时间考验的 VPN 协议之一。 +它采用其独特的安全协议,利用 SSL/TLS 的优势进行加密和密钥交换。此外,OpenVPN 支持多种身份验证方法,使其具有多功能性和适应性,可适应各种设备和操作系统。由于其开源性质,OpenVPN 受益于全球社区的广泛审查,这不断增强了其安全性。凭借性能、安全性和兼容性的强大平衡,OpenVPN 仍然是注重隐私的个人和企业的首选。 + +* 可在所有平台的 AmneziaVPN 中使用 +* 移动设备的正常功耗 +* 灵活定制,满足用户使用不同操作系统和设备的需求 +* 被DPI分析系统识别,因此容易被阻塞 +* 可以通过 TCP 和 UDP 网络协议运行 @@ -2622,7 +2630,14 @@ It employs its unique security protocol, leveraging the strength of SSL/TLS for * Configurable encryption protocol * Detectable by some DPI systems * Works over TCP network protocol. - + Shadowsocks 受到 SOCKS5 协议的启发,使用 AEAD 密码保护连接。尽管 Shadowsocks 设计得谨慎且难以识别,但它与标准 HTTPS 连接并不相同。但是,某些流量分析系统可能仍会检测到 Shadowsocks 连接。由于Amnezia支持有限,建议使用AmneziaWG协议。 + +* 仅在桌面平台上的 AmneziaVPN 中可用 +* 移动设备的正常功耗 + +* 可配置的加密协议 +* 可以被某些 DPI 系统检测到 +* 通过 TCP 网络协议工作。 @@ -2644,7 +2659,23 @@ If there is a extreme level of Internet censorship in your region, we advise you * Not recognised by DPI analysis systems * Works over TCP network protocol, 443 port. - + 这是 OpenVPN 协议和专门用于阻止保护的 Cloak 插件的组合。 + +OpenVPN 通过加密客户端和服务器之间的所有 Internet 流量来提供安全的 VPN 连接。 + +Cloak 可保护 OpenVPN 免遭检测和阻止。 + +Cloak 可以修改数据包元数据,以便将 VPN 流量完全屏蔽为正常 Web 流量,并且还可以保护 VPN 免受主动探测的检测。这使得它非常难以被发现 + +收到第一个数据包后,Cloak 立即对传入连接进行身份验证。如果身份验证失败,该插件会将服务器伪装成虚假网站,并且您的 VPN 对分析系统来说将变得不可见。 + +如果您所在地区的互联网审查非常严格,我们建议您在第一次连接时仅使用 OpenVPN over Cloak + +* 可在所有平台的 AmneziaVPN 中使用 +* 移动设备功耗高 +* 配置灵活 +* 不被 DPI 分析系统识别 +* 通过 TCP 网络协议、443 端口工作。 @@ -2657,7 +2688,15 @@ WireGuard is very susceptible to blocking due to its distinct packet signatures. * Minimum number of settings * Easily recognised by DPI analysis systems, susceptible to blocking * Works over UDP network protocol. - + 一种相对较新的流行 VPN 协议,具有简化的架构。 +在所有设备上提供稳定的 VPN 连接和高性能。使用硬编码的加密设置。 WireGuard 与 OpenVPN 相比具有更低的延迟和更好的数据传输吞吐量。 +由于其独特的数据包签名,WireGuard 非常容易受到阻塞。与其他一些采用混淆技术的 VPN 协议不同,WireGuard 数据包的一致签名模式可以更容易地被高级深度数据包检测 (DPI) 系统和其他网络监控工具识别并阻止。 + +* 可在所有平台的 AmneziaVPN 中使用 +* 低功耗 +* 配置简单 +* 容易被DPI分析系统识别,容易被阻塞 +* 通过 UDP 网络协议工作。 @@ -2670,7 +2709,15 @@ This means that AmneziaWG keeps the fast performance of the original while addin * Minimum number of settings * Not recognised by DPI analysis systems, resistant to blocking * Works over UDP network protocol. - + AmneziaWG 是流行 VPN 协议的现代迭代,它建立在 WireGuard 的基础上,保留了其简化的架构和跨设备的高性能功能。 +虽然 WireGuard 以其高效而闻名,但由于其独特的数据包签名,它存在容易被检测到的问题。 AmneziaWG 通过使用更好的混淆方法解决了这个问题,使其流量与常规互联网流量融合在一起。 +这意味着 AmneziaWG 保留了原始版本的快速性能,同时添加了额外的隐秘层,使其成为那些想要快速且谨慎的 VPN 连接的人的绝佳选择。 + +* 可在所有平台的 AmneziaVPN 中使用 +* 低功耗 +* 配置简单 +* 不被DPI分析系统识别,抗阻塞 +* 通过 UDP 网络协议工作。 @@ -2683,7 +2730,15 @@ While it offers a blend of security, stability, and speed, it's essential t * Minimal configuration * Recognised by DPI analysis systems * Works over UDP network protocol, ports 500 and 4500. - + IKEv2 与 IPSec 加密层配合使用,是一种现代且稳定的 VPN 协议。 +其显着特征之一是能够在网络和设备之间快速切换,使其特别适应动态网络环境。 +虽然 IKEv2 兼具安全性、稳定性和速度,但必须注意的是,IKEv2 很容易被检测到,并且容易受到阻止。 + +* 仅在 Windows 上的 AmneziaVPN 中可用 +* 低功耗,在移动设备上 +* 最低配置 +* 获得DPI分析系统认可 +* 通过 UDP 网络协议、端口 500 和 4500 工作。 OpenVPN container @@ -3004,27 +3059,27 @@ While it offers a blend of security, stability, and speed, it's essential t Medium or High - + 中或高 Extreme - + 极度 I just want to increase the level of my privacy. - + 只是想提高隐私保护级别。 I want to bypass censorship. This option recommended in most cases. - + 想要绕过审查制度。大多数情况下推荐使用此选项。 Most VPN protocols are blocked. Recommended if other options are not working. - + 大多数 VPN 协议都被阻止。如果其他选项不起作用,推荐此选项。 High From 03171e474331eec48be2717851a77359663c3139 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Tue, 17 Oct 2023 19:34:34 +0800 Subject: [PATCH 245/278] update background color and drag-effect, moved dulicated code --- .../qml/Components/HomeContainersListView.qml | 2 +- client/ui/qml/Controls2/Drawer2Type.qml | 27 ++++++++++--------- client/ui/qml/Controls2/DropDownType.qml | 2 +- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index 1436c747..c6c4125a 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -69,7 +69,7 @@ ListView { isDefault = true menuContent.currentIndex = index - containersDropDown.menu.state = "closed" + containersDropDown.menu.close() if (needReconnected && diff --git a/client/ui/qml/Controls2/Drawer2Type.qml b/client/ui/qml/Controls2/Drawer2Type.qml index 3f3cf54b..fcd773a8 100644 --- a/client/ui/qml/Controls2/Drawer2Type.qml +++ b/client/ui/qml/Controls2/Drawer2Type.qml @@ -10,7 +10,7 @@ Item { target: PageController function onForceCloseDrawer() { - root.state = "closed" + close() } } @@ -32,8 +32,6 @@ Item { state: "closed" - Drag.active: dragArea.drag.active - Rectangle { id: draw2Background @@ -41,7 +39,7 @@ Item { height: root.parent.height width: parent.width radius: 16 - color: "transparent" + color: "#90000000" border.color: "transparent" border.width: 1 visible: true @@ -54,6 +52,7 @@ Item { onClicked: { if (root.state === "opened") { + draw2Background.color = "transparent" root.state = "closed" return } @@ -61,14 +60,16 @@ Item { Rectangle { id: placeAreaHolder - anchors.top: parent.top height: parent.height - contentHeight anchors.right: parent.right anchors.left: parent.left visible: true color: "transparent" + + Drag.active: dragArea.drag.active } + Rectangle { id: contentArea @@ -98,27 +99,27 @@ Item { cursorShape: (root.state === "opened") ? Qt.PointingHandCursor : Qt.ArrowCursor hoverEnabled: true - drag.target: root + drag.target: placeAreaHolder drag.axis: Drag.YAxis drag.maximumY: root.height drag.minimumY: root.height - root.height /** If drag area is released at any point other than min or max y, transition to the other state */ onReleased: { - if (root.state === "closed" && root.y < dragArea.drag.maximumY) { + if (root.state === "closed" && placeAreaHolder.y < dragArea.drag.maximumY) { root.state = "opened" return } - if (root.state === "opened" && root.y > dragArea.drag.minimumY) { - root.state = "closed" + if (root.state === "opened" && placeAreaHolder.y > dragArea.drag.minimumY) { + close() return } } onClicked: { if (root.state === "opened") { - root.state = "closed" + close() return } } @@ -209,22 +210,24 @@ Item { if (root.visible && root.state !== "closed") { return } + draw2Background.color = "#90000000" root.y = 0 root.state = "opened" root.visible = true root.height = parent.height contentArea.height = contentHeight + placeAreaHolder.height = parent.height - contentHeight + placeAreaHolder.y = parent.height - root.height dragArea.drag.maximumY = parent.height dragArea.drag.minimumY = parent.height - root.height - animationVisible.running = true } function close() { - root.visible = false + draw2Background.color = "transparent" root.state = "closed" } diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index 0506bdc7..c666408a 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -187,7 +187,7 @@ Item { BackButtonType { backButtonImage: root.headerBackButtonImage backButtonFunction: function() { - menu.state = "closed" + menu.close() } } } From 61ddfe01a10f3149711e8405a2218264ea8ee534 Mon Sep 17 00:00:00 2001 From: pokamest Date: Tue, 17 Oct 2023 06:39:49 -0700 Subject: [PATCH 246/278] macos build script updated [no ci] --- deploy/build_macos.sh | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/deploy/build_macos.sh b/deploy/build_macos.sh index 700198e7..13214d6d 100755 --- a/deploy/build_macos.sh +++ b/deploy/build_macos.sh @@ -96,16 +96,16 @@ if [ "${MAC_CERT_PW+x}" ]; then security find-identity -p codesigning echo "Signing App bundle..." - /usr/bin/codesign --deep --force --verbose --timestamp -o runtime --sign "Developer ID Application: Privacy Technologies OU (X7UJ388FXK)" $BUNDLE_DIR + /usr/bin/codesign --deep --force --verbose --timestamp -o runtime --sign "$MAC_SIGNER_ID" $BUNDLE_DIR /usr/bin/codesign --verify -vvvv $BUNDLE_DIR || true spctl -a -vvvv $BUNDLE_DIR || true if [ "${NOTARIZE_APP+x}" ]; then echo "Notarizing App bundle..." /usr/bin/ditto -c -k --keepParent $BUNDLE_DIR $PROJECT_DIR/Bundle_to_notarize.zip - xcrun altool --notarize-app -f $PROJECT_DIR/Bundle_to_notarize.zip -t osx --primary-bundle-id "$APP_DOMAIN" -u "$APPLE_DEV_EMAIL" -p $APPLE_DEV_PASSWORD + xcrun notarytool submit $PROJECT_DIR/Bundle_to_notarize.zip --apple-id $APPLE_DEV_EMAIL --team-id $MAC_TEAM_ID --password $APPLE_DEV_PASSWORD rm $PROJECT_DIR/Bundle_to_notarize.zip - sleep 600 + sleep 300 xcrun stapler staple $BUNDLE_DIR xcrun stapler validate $BUNDLE_DIR spctl -a -vvvv $BUNDLE_DIR || true @@ -130,15 +130,15 @@ $QIF_BIN_DIR/binarycreator --offline-only -v -c $BUILD_DIR/installer/config/maco if [ "${MAC_CERT_PW+x}" ]; then echo "Signing installer bundle..." security unlock-keychain -p $TEMP_PASS $KEYCHAIN - /usr/bin/codesign --deep --force --verbose --timestamp -o runtime --sign "Developer ID Application: Privacy Technologies OU (X7UJ388FXK)" $INSTALLER_BUNDLE_DIR + /usr/bin/codesign --deep --force --verbose --timestamp -o runtime --sign "$MAC_SIGNER_ID" $INSTALLER_BUNDLE_DIR /usr/bin/codesign --verify -vvvv $INSTALLER_BUNDLE_DIR || true if [ "${NOTARIZE_APP+x}" ]; then echo "Notarizing installer bundle..." /usr/bin/ditto -c -k --keepParent $INSTALLER_BUNDLE_DIR $PROJECT_DIR/Installer_bundle_to_notarize.zip - xcrun altool --notarize-app -f $PROJECT_DIR/Installer_bundle_to_notarize.zip -t osx --primary-bundle-id "$APP_DOMAIN" -u "$APPLE_DEV_EMAIL" -p $APPLE_DEV_PASSWORD + xcrun notarytool submit $PROJECT_DIR/Installer_bundle_to_notarize.zip --apple-id $APPLE_DEV_EMAIL --team-id $MAC_TEAM_ID --password $APPLE_DEV_PASSWORD rm $PROJECT_DIR/Installer_bundle_to_notarize.zip - sleep 600 + sleep 300 xcrun stapler staple $INSTALLER_BUNDLE_DIR xcrun stapler validate $INSTALLER_BUNDLE_DIR spctl -a -vvvv $INSTALLER_BUNDLE_DIR || true @@ -151,13 +151,13 @@ hdiutil create -volname AmneziaVPN -srcfolder $BUILD_DIR/installer/$APP_NAME.app if [ "${MAC_CERT_PW+x}" ]; then echo "Signing DMG installer..." security unlock-keychain -p $TEMP_PASS $KEYCHAIN - /usr/bin/codesign --deep --force --verbose --timestamp -o runtime --sign "Developer ID Application: Privacy Technologies OU (X7UJ388FXK)" $DMG_FILENAME + /usr/bin/codesign --deep --force --verbose --timestamp -o runtime --sign "$MAC_SIGNER_ID" $DMG_FILENAME /usr/bin/codesign --verify -vvvv $DMG_FILENAME || true if [ "${NOTARIZE_APP+x}" ]; then echo "Notarizing DMG installer..." - xcrun altool --notarize-app -f $DMG_FILENAME -t osx --primary-bundle-id $APP_DOMAIN -u $APPLE_DEV_EMAIL -p $APPLE_DEV_PASSWORD - sleep 600 + xcrun notarytool submit $DMG_FILENAME --apple-id $APPLE_DEV_EMAIL --team-id $MAC_TEAM_ID --password $APPLE_DEV_PASSWORD + sleep 300 xcrun stapler staple $DMG_FILENAME xcrun stapler validate $DMG_FILENAME fi From 94304b5777a6e74546f6aaa3f1d61126a019f940 Mon Sep 17 00:00:00 2001 From: pokamest Date: Tue, 17 Oct 2023 14:47:31 +0100 Subject: [PATCH 247/278] Version bump --- CMakeLists.txt | 2 +- client/ui/qml/Pages2/PageSettingsConnection.qml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 85d7d0ce..028aacd3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.0.8.2 +project(${PROJECT} VERSION 4.0.8.3 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml index 7f0262f9..565ae7db 100644 --- a/client/ui/qml/Pages2/PageSettingsConnection.qml +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -94,7 +94,7 @@ PageType { DividerType {} LabelWithButtonType { - visible: !GC.isMobile() + visible: GC.isDesktop() Layout.fillWidth: true @@ -108,11 +108,11 @@ PageType { } DividerType { - visible: !GC.isMobile() + visible: GC.isDesktop() } LabelWithButtonType { - visible: !GC.isMobile() + visible: false Layout.fillWidth: true @@ -125,7 +125,7 @@ PageType { } DividerType { - visible: !GC.isMobile() + visible: false } } } From a83cd29f725136094bfb5d8855b9167b761bb125 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Tue, 17 Oct 2023 22:00:19 +0800 Subject: [PATCH 248/278] fixed the cursorShape, and some minor issues --- client/ui/qml/Controls2/Drawer2Type.qml | 47 ++++++++++++++----------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/client/ui/qml/Controls2/Drawer2Type.qml b/client/ui/qml/Controls2/Drawer2Type.qml index fcd773a8..988b4172 100644 --- a/client/ui/qml/Controls2/Drawer2Type.qml +++ b/client/ui/qml/Controls2/Drawer2Type.qml @@ -28,15 +28,13 @@ Item { property int contentHeight: 0 property Item contentParent: contentArea - y: parent.height - root.height - state: "closed" Rectangle { id: draw2Background - anchors { left: root.left; right: root.right; top: root.top } - height: root.parent.height + anchors.fill: parent + height: parent.height width: parent.width radius: 16 color: "#90000000" @@ -45,15 +43,14 @@ Item { visible: true MouseArea { - id: fullArea + id: fullMouseArea anchors.fill: parent enabled: (root.state === "opened") hoverEnabled: true onClicked: { if (root.state === "opened") { - draw2Background.color = "transparent" - root.state = "closed" + close() return } } @@ -106,15 +103,17 @@ Item { /** If drag area is released at any point other than min or max y, transition to the other state */ onReleased: { - if (root.state === "closed" && placeAreaHolder.y < dragArea.drag.maximumY) { + if (root.state === "closed" && placeAreaHolder.y < root.height * 0.9) { root.state = "opened" return } - if (root.state === "opened" && placeAreaHolder.y > dragArea.drag.minimumY) { + if (root.state === "opened" && placeAreaHolder.y > (root.height - root.height * 0.9)) { close() return } + + placeAreaHolder.y = 0 } onClicked: { @@ -161,15 +160,15 @@ Item { State { name: "closed" PropertyChanges { - target: root + target: placeAreaHolder y: parent.height } }, State { - name: "opend" + name: "opened" PropertyChanges { - target: root + target: placeAreaHolder y: dragArea.drag.minimumY } } @@ -180,7 +179,7 @@ Item { from: "opened" to: "closed" PropertyAnimation { - target: root + target: placeAreaHolder properties: "y" duration: 200 } @@ -190,7 +189,7 @@ Item { from: "closed" to: "opened" PropertyAnimation { - target: root + target: placeAreaHolder properties: "y" duration: 200 } @@ -199,7 +198,7 @@ Item { NumberAnimation { id: animationVisible - target: root + target: placeAreaHolder property: "y" from: parent.height to: 0 @@ -212,21 +211,29 @@ Item { } draw2Background.color = "#90000000" + fullMouseArea.visible = true + dragArea.visible = true + root.y = 0 root.state = "opened" root.visible = true root.height = parent.height - contentArea.height = contentHeight - placeAreaHolder.height = parent.height - contentHeight - placeAreaHolder.y = parent.height - root.height - dragArea.drag.maximumY = parent.height - dragArea.drag.minimumY = parent.height - root.height + contentArea.height = contentHeight + + placeAreaHolder.height = root.height - contentHeight + placeAreaHolder.y = root.height - root.height + + dragArea.drag.maximumY = root.height + dragArea.drag.minimumY = 0 animationVisible.running = true } function close() { + fullMouseArea.visible = false + dragArea.visible = false + draw2Background.color = "transparent" root.state = "closed" } From 160d88f002ee612ec93e15d09e0b963ef5bcf646 Mon Sep 17 00:00:00 2001 From: lunardunno <126363523+lunardunno@users.noreply.github.com> Date: Tue, 17 Oct 2023 21:26:50 +0400 Subject: [PATCH 249/278] Restoring autostart and enable docker for CentOS 7 --- client/server_scripts/install_docker.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/server_scripts/install_docker.sh b/client/server_scripts/install_docker.sh index e780dac5..58f92540 100644 --- a/client/server_scripts/install_docker.sh +++ b/client/server_scripts/install_docker.sh @@ -8,7 +8,7 @@ if ! command -v sudo > /dev/null 2>&1; then $pm update -yq; $pm install -yq sudo if ! command -v fuser > /dev/null 2>&1; then sudo $pm install -yq psmisc; fi;\ if ! command -v lsof > /dev/null 2>&1; then sudo $pm install -yq lsof; fi;\ if ! command -v docker > /dev/null 2>&1; then sudo $pm update -yq; sudo $pm install -yq $docker_pkg;\ - if [ "$dist" = "fedora" ] || [ "$dist" = "debian" ]; then sudo systemctl enable docker && sudo systemctl start docker; fi;\ + if [ "$dist" = "fedora" ] || [ "$dist" = "centos" ] || [ "$dist" = "debian" ]; then sudo systemctl enable docker && sudo systemctl start docker; fi;\ fi;\ if [ "$dist" = "debian" ]; then \ docker_service=$(systemctl list-units --full --all | grep docker.service | grep -v inactive | grep -v dead | grep -v failed);\ From 2f0c1eeecc11165c1928a6b07e75859e6ec4e629 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 18 Oct 2023 00:36:40 +0500 Subject: [PATCH 250/278] fixed selection of default container after installing a new server --- client/amnezia_application.cpp | 2 ++ client/ui/controllers/installController.cpp | 2 ++ client/ui/models/servers_model.cpp | 2 +- client/ui/models/servers_model.h | 2 +- client/ui/qml/Components/ConnectButton.qml | 1 + 5 files changed, 7 insertions(+), 2 deletions(-) diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 4e6bce2b..bbd48c96 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -286,6 +286,8 @@ void AmneziaApplication::initModels() m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get()); connect(m_serversModel.get(), &ServersModel::currentlyProcessedServerIndexChanged, m_containersModel.get(), &ContainersModel::setCurrentlyProcessedServerIndex); + connect(m_serversModel.get(), &ServersModel::defaultServerIndexChanged, m_containersModel.get(), + &ContainersModel::setCurrentlyProcessedServerIndex); m_languageModel.reset(new LanguageModel(m_settings, this)); m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get()); diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index bb10d39c..db2f9409 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -475,6 +475,8 @@ void InstallController::addEmptyServer() server.insert(config_key::port, m_currentlyInstalledServerCredentials.port); server.insert(config_key::description, m_settings->nextAvailableServerName()); + server.insert(config_key::defaultContainer, ContainerProps::containerToString(DockerContainer::None)); + m_serversModel->addServer(server); m_serversModel->setDefaultServerIndex(m_serversModel->getServersCount() - 1); diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 7eea94e5..a2a28630 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -96,7 +96,7 @@ void ServersModel::setDefaultServerIndex(const int index) { m_settings->setDefaultServer(index); m_defaultServerIndex = m_settings->defaultServerIndex(); - emit defaultServerIndexChanged(); + emit defaultServerIndexChanged(m_defaultServerIndex); } const int ServersModel::getDefaultServerIndex() diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index d7b15844..ad1d5a83 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -64,7 +64,7 @@ protected: signals: void currentlyProcessedServerIndexChanged(const int index); - void defaultServerIndexChanged(); + void defaultServerIndexChanged(const int index); void defaultServerNameChanged(); private: diff --git a/client/ui/qml/Components/ConnectButton.qml b/client/ui/qml/Components/ConnectButton.qml index 76e83da5..c2ec186d 100644 --- a/client/ui/qml/Components/ConnectButton.qml +++ b/client/ui/qml/Components/ConnectButton.qml @@ -142,6 +142,7 @@ Button { PageController.setTriggeredBtConnectButton(true) ServersModel.currentlyProcessedIndex = ServersModel.getDefaultServerIndex() + InstallController.setShouldCreateServer(false) PageController.goToPage(PageEnum.PageSetupWizardEasy) return From 4b64bfaec029aca8355ed627aeaa8356ba243706 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 18 Oct 2023 00:37:15 +0500 Subject: [PATCH 251/278] fixed questionDrawer height --- client/translations/amneziavpn_ru.ts | 14 ++++++++++++-- client/translations/amneziavpn_zh_CN.ts | 14 ++++++++++++-- client/ui/qml/Components/QuestionDrawer.qml | 4 +++- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index 1617413a..4d3ae6c9 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -4,7 +4,7 @@ AmneziaApplication - + Split tunneling for WireGuard is not implemented, the option was disabled Раздельное туннелирование для "Wireguard" не реализовано,опция отключена @@ -194,7 +194,7 @@ Already installed containers were found on the server. All installed containers Пожалуйста, войдите в систему от имени пользователя - + Server added successfully Сервер успешно добавлен @@ -2700,6 +2700,16 @@ This means that AmneziaWG keeps the fast performance of the original while addin error 0x%1: %2 error 0x%1: %2 + + + WireGuard Configuration Highlighter + + + + + &Randomize colors + + SelectLanguageDrawer diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index 09198c9c..ae25e41c 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -4,7 +4,7 @@ AmneziaApplication - + Split tunneling for WireGuard is not implemented, the option was disabled 未启用选项,还未实现基于WireGuard协议的VPN分离 @@ -228,7 +228,7 @@ Already installed containers were found on the server. All installed containers 请以用户身份登录 - + Server added successfully 增加服务器成功 @@ -2839,6 +2839,16 @@ While it offers a blend of security, stability, and speed, it's essential t error 0x%1: %2 错误 0x%1: %2 + + + WireGuard Configuration Highlighter + + + + + &Randomize colors + + SelectLanguageDrawer diff --git a/client/ui/qml/Components/QuestionDrawer.qml b/client/ui/qml/Components/QuestionDrawer.qml index a79f9140..16cdcb39 100644 --- a/client/ui/qml/Components/QuestionDrawer.qml +++ b/client/ui/qml/Components/QuestionDrawer.qml @@ -17,9 +17,11 @@ DrawerType { property var noButtonFunction width: parent.width - height: parent.height * 0.5 + height: content.implicitHeight + 32 ColumnLayout { + id: content + anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right From a46e55d5c2b938f06ddd03e510e6f9470a538504 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 18 Oct 2023 01:11:41 +0500 Subject: [PATCH 252/278] added a dash for drawerType --- client/translations/amneziavpn_ru.ts | 6 +++--- client/translations/amneziavpn_zh_CN.ts | 6 +++--- client/ui/qml/Controls2/DrawerType.qml | 14 ++++++++++++++ client/ui/qml/Pages2/PageHome.qml | 14 ++++++++++++-- 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index 4d3ae6c9..db27afbc 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -262,17 +262,17 @@ Already installed containers were found on the server. All installed containers PageHome - + VPN protocol VPN протокол - + Servers Серверы - + Unable change server while there is an active connection diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index ae25e41c..59bef770 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -296,17 +296,17 @@ Already installed containers were found on the server. All installed containers PageHome - + VPN protocol VPN协议 - + Servers 服务器 - + Unable change server while there is an active connection Невозможно изменить сервер при наличии активного соединения diff --git a/client/ui/qml/Controls2/DrawerType.qml b/client/ui/qml/Controls2/DrawerType.qml index 72765d78..830f59f9 100644 --- a/client/ui/qml/Controls2/DrawerType.qml +++ b/client/ui/qml/Controls2/DrawerType.qml @@ -1,6 +1,8 @@ import QtQuick import QtQuick.Controls +import "../Config" + Drawer { id: drawer property bool needCloseButton: true @@ -39,6 +41,18 @@ Drawer { border.color: "#2C2D30" border.width: 1 + + Rectangle { + visible: GC.isMobile() + + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + anchors.topMargin: 10 + + width: 20 + height: 2 + color: "#2C2D30" + } } Overlay.modal: Rectangle { diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index d89d6be1..3a71adac 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -241,8 +241,18 @@ PageType { } ] + DividerType { + Layout.topMargin: 10 + Layout.fillWidth: false + Layout.preferredWidth: 20 + Layout.preferredHeight: 2 + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + + visible: GC.isMobile() && (buttonContent.collapsedVisibility || buttonContent.expandedVisibility) + } + RowLayout { - Layout.topMargin: 24 + Layout.topMargin: 14 Layout.leftMargin: 24 Layout.rightMargin: 24 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter @@ -305,7 +315,7 @@ PageType { Header1TextType { Layout.fillWidth: true - Layout.topMargin: 24 + Layout.topMargin: 14 Layout.leftMargin: 16 Layout.rightMargin: 16 From f5f72f87a6246ee320aa027348e18edfae97cacc Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 18 Oct 2023 12:17:24 +0500 Subject: [PATCH 253/278] fixed switcher status display for page split site tunneling --- client/ui/models/sites_model.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ui/models/sites_model.cpp b/client/ui/models/sites_model.cpp index 1e0f1692..5f4afe1a 100644 --- a/client/ui/models/sites_model.cpp +++ b/client/ui/models/sites_model.cpp @@ -8,6 +8,7 @@ SitesModel::SitesModel(std::shared_ptr settings, QObject *parent) m_isSplitTunnelingEnabled = false; m_currentRouteMode = Settings::RouteMode::VpnOnlyForwardSites; } else { + m_isSplitTunnelingEnabled = true; m_currentRouteMode = routeMode; } fillSites(); From c461e00c5c378e5ed6ade5291b2233947a94fe60 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Wed, 18 Oct 2023 16:17:57 +0800 Subject: [PATCH 254/278] keeping parent's cusorshape and Drawer2Type's close-animation --- client/ui/qml/Controls2/Drawer2Type.qml | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/client/ui/qml/Controls2/Drawer2Type.qml b/client/ui/qml/Controls2/Drawer2Type.qml index 988b4172..b34390ac 100644 --- a/client/ui/qml/Controls2/Drawer2Type.qml +++ b/client/ui/qml/Controls2/Drawer2Type.qml @@ -183,6 +183,12 @@ Item { properties: "y" duration: 200 } + + onRunningChanged: { + if (!running) { + visibledMouseArea(false) + } + } }, Transition { @@ -211,8 +217,7 @@ Item { } draw2Background.color = "#90000000" - fullMouseArea.visible = true - dragArea.visible = true + visibledMouseArea(true) root.y = 0 root.state = "opened" @@ -231,9 +236,6 @@ Item { } function close() { - fullMouseArea.visible = false - dragArea.visible = false - draw2Background.color = "transparent" root.state = "closed" } @@ -250,4 +252,9 @@ Item { close() } } + + function visibledMouseArea(visbile) { + fullMouseArea.visible = visbile + dragArea.visible = visbile + } } From e16c425f875d209f4f35fe7f94714b46593352e0 Mon Sep 17 00:00:00 2001 From: pokamest Date: Wed, 18 Oct 2023 12:04:39 +0100 Subject: [PATCH 255/278] PageHome.qml fix --- CMakeLists.txt | 2 +- client/translations/amneziavpn_ru.ts | 10 ---------- client/translations/amneziavpn_zh_CN.ts | 10 ---------- client/ui/qml/Pages2/PageHome.qml | 2 +- 4 files changed, 2 insertions(+), 22 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 028aacd3..68de51fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.0.8.3 +project(${PROJECT} VERSION 4.0.8.4 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index db27afbc..a26758b7 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -2700,16 +2700,6 @@ This means that AmneziaWG keeps the fast performance of the original while addin error 0x%1: %2 error 0x%1: %2 - - - WireGuard Configuration Highlighter - - - - - &Randomize colors - - SelectLanguageDrawer diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index 59bef770..a10fb449 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -2839,16 +2839,6 @@ While it offers a blend of security, stability, and speed, it's essential t error 0x%1: %2 错误 0x%1: %2 - - - WireGuard Configuration Highlighter - - - - - &Randomize colors - - SelectLanguageDrawer diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 3a71adac..cc49e4f0 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -248,7 +248,7 @@ PageType { Layout.preferredHeight: 2 Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - visible: GC.isMobile() && (buttonContent.collapsedVisibility || buttonContent.expandedVisibility) + visible: (buttonContent.collapsedVisibility || buttonContent.expandedVisibility) } RowLayout { From e2ae341ba96b375e381b2a6346563e5a1dddd0a2 Mon Sep 17 00:00:00 2001 From: pokamest Date: Wed, 18 Oct 2023 14:01:06 +0100 Subject: [PATCH 256/278] AndroidManifest fix --- client/android/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/client/android/AndroidManifest.xml b/client/android/AndroidManifest.xml index 4ec807e9..1115b74d 100644 --- a/client/android/AndroidManifest.xml +++ b/client/android/AndroidManifest.xml @@ -45,6 +45,7 @@ android:label="-- %%INSERT_APP_NAME%% --" android:screenOrientation="unspecified" android:launchMode="singleInstance" + android:windowSoftInputMode="adjustResize" android:exported="true"> From 79e1761c1f476e2608ad2c93ff024f829509dce5 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 19 Oct 2023 01:14:09 +0500 Subject: [PATCH 257/278] added generation of random values for awg parameters --- client/core/servercontroller.cpp | 37 +++++++-------------- client/translations/amneziavpn_ru.ts | 34 ++++++++++++------- client/translations/amneziavpn_zh_CN.ts | 34 ++++++++++++------- client/ui/controllers/installController.cpp | 31 +++++++++++++++++ 4 files changed, 87 insertions(+), 49 deletions(-) diff --git a/client/core/servercontroller.cpp b/client/core/servercontroller.cpp index 60691759..443cd5a3 100644 --- a/client/core/servercontroller.cpp +++ b/client/core/servercontroller.cpp @@ -337,7 +337,7 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c != newProtoConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort)) return true; } - + if (container == DockerContainer::Awg) { return true; } @@ -490,8 +490,7 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential const QJsonObject &cloakConfig = config.value(ProtocolProps::protoToString(Proto::Cloak)).toObject(); const QJsonObject &ssConfig = config.value(ProtocolProps::protoToString(Proto::ShadowSocks)).toObject(); const QJsonObject &wireguarConfig = config.value(ProtocolProps::protoToString(Proto::WireGuard)).toObject(); - const QJsonObject &amneziaWireguarConfig = - config.value(ProtocolProps::protoToString(Proto::Awg)).toObject(); + const QJsonObject &amneziaWireguarConfig = config.value(ProtocolProps::protoToString(Proto::Awg)).toObject(); const QJsonObject &sftpConfig = config.value(ProtocolProps::protoToString(Proto::Sftp)).toObject(); Vars vars; @@ -591,33 +590,21 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential // Amnezia wireguard vars vars.append({ { "$AWG_SERVER_PORT", amneziaWireguarConfig.value(config_key::port).toString(protocols::awg::defaultPort) } }); - vars.append({ { "$JUNK_PACKET_COUNT", - amneziaWireguarConfig.value(config_key::junkPacketCount) - .toString(protocols::awg::defaultJunkPacketCount) } }); - vars.append({ { "$JUNK_PACKET_MIN_SIZE", - amneziaWireguarConfig.value(config_key::junkPacketMinSize) - .toString(protocols::awg::defaultJunkPacketMinSize) } }); - vars.append({ { "$JUNK_PACKET_MAX_SIZE", - amneziaWireguarConfig.value(config_key::junkPacketMaxSize) - .toString(protocols::awg::defaultJunkPacketMaxSize) } }); - vars.append({ { "$INIT_PACKET_JUNK_SIZE", - amneziaWireguarConfig.value(config_key::initPacketJunkSize) - .toString(protocols::awg::defaultInitPacketJunkSize) } }); + + vars.append({ { "$JUNK_PACKET_COUNT", amneziaWireguarConfig.value(config_key::junkPacketCount).toString() } }); + vars.append({ { "$JUNK_PACKET_MIN_SIZE", amneziaWireguarConfig.value(config_key::junkPacketMinSize).toString() } }); + vars.append({ { "$JUNK_PACKET_MAX_SIZE", amneziaWireguarConfig.value(config_key::junkPacketMaxSize).toString() } }); + vars.append({ { "$INIT_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::initPacketJunkSize).toString() } }); vars.append({ { "$RESPONSE_PACKET_JUNK_SIZE", - amneziaWireguarConfig.value(config_key::responsePacketJunkSize) - .toString(protocols::awg::defaultResponsePacketJunkSize) } }); + amneziaWireguarConfig.value(config_key::responsePacketJunkSize).toString() } }); vars.append({ { "$INIT_PACKET_MAGIC_HEADER", - amneziaWireguarConfig.value(config_key::initPacketMagicHeader) - .toString(protocols::awg::defaultInitPacketMagicHeader) } }); + amneziaWireguarConfig.value(config_key::initPacketMagicHeader).toString() } }); vars.append({ { "$RESPONSE_PACKET_MAGIC_HEADER", - amneziaWireguarConfig.value(config_key::responsePacketMagicHeader) - .toString(protocols::awg::defaultResponsePacketMagicHeader) } }); + amneziaWireguarConfig.value(config_key::responsePacketMagicHeader).toString() } }); vars.append({ { "$UNDERLOAD_PACKET_MAGIC_HEADER", - amneziaWireguarConfig.value(config_key::underloadPacketMagicHeader) - .toString(protocols::awg::defaultUnderloadPacketMagicHeader) } }); + amneziaWireguarConfig.value(config_key::underloadPacketMagicHeader).toString() } }); vars.append({ { "$TRANSPORT_PACKET_MAGIC_HEADER", - amneziaWireguarConfig.value(config_key::transportPacketMagicHeader) - .toString(protocols::awg::defaultTransportPacketMagicHeader) } }); + amneziaWireguarConfig.value(config_key::transportPacketMagicHeader).toString() } }); QString serverIp = Utils::getIPAddress(credentials.hostName); if (!serverIp.isEmpty()) { diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index a26758b7..733c374d 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -143,58 +143,58 @@ InstallController - - + + %1 installed successfully. %1 успешно установлен. - - + + %1 is already installed on the server. %1 уже установлен на сервер. - + Added containers that were already installed on the server В приложение добавлены обнаруженные на сервере протоклы и сервисы - + Already installed containers were found on the server. All installed containers have been added to the application На сервере обнаружены установленные протоколы и сервисы, все они добавлены в приложение - + Settings updated successfully Настройки успешно обновлены - + Server '%1' was removed Сервер '%1' был удален - + All containers from server '%1' have been removed Все протоклы и сервисы были удалены с сервера '%1' - + %1 has been removed from the server '%2' %1 был удален с сервера '%2' - + Please login as the user Пожалуйста, войдите в систему от имени пользователя - + Server added successfully Сервер успешно добавлен @@ -2700,6 +2700,16 @@ This means that AmneziaWG keeps the fast performance of the original while addin error 0x%1: %2 error 0x%1: %2 + + + WireGuard Configuration Highlighter + + + + + &Randomize colors + + SelectLanguageDrawer diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index a10fb449..64faa6f4 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -162,47 +162,47 @@ 已安装在服务器上 - - + + %1 installed successfully. %1 安装成功。 - - + + %1 is already installed on the server. 服务器上已经安装 %1。 - + Added containers that were already installed on the server 添加已安装在服务器上的容器 - + Already installed containers were found on the server. All installed containers have been added to the application 在服务上发现已经安装协议并添加至应用 - + Settings updated successfully 配置更新成功 - + Server '%1' was removed 已移除服务器 '%1' - + All containers from server '%1' have been removed 服务器 '%1' 的所有容器已移除 - + %1 has been removed from the server '%2' %1 已从服务器 '%2' 上移除 @@ -223,12 +223,12 @@ Already installed containers were found on the server. All installed containers 协议已从 - + Please login as the user 请以用户身份登录 - + Server added successfully 增加服务器成功 @@ -2839,6 +2839,16 @@ While it offers a blend of security, stability, and speed, it's essential t error 0x%1: %2 错误 0x%1: %2 + + + WireGuard Configuration Highlighter + + + + + &Randomize colors + + SelectLanguageDrawer diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index db2f9409..8efe368b 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "core/errorstrings.h" #include "core/servercontroller.h" @@ -73,6 +74,36 @@ void InstallController::install(DockerContainer container, int port, TransportPr containerConfig.insert(config_key::transport_proto, ProtocolProps::transportProtoToString(transportProto, protocol)); + if (container == DockerContainer::Awg) { + QString defaultJunkPacketCount = QString::number(QRandomGenerator::global()->bounded(3, 10)); + QString defaultJunkPacketMinSize = QString::number(50); + QString defaultJunkPacketMaxSize = QString::number(1000); + QString defaultInitPacketJunkSize = QString::number(QRandomGenerator::global()->bounded(15, 150)); + QString defaultResponsePacketJunkSize = QString::number(QRandomGenerator::global()->bounded(15, 150)); + + QSet headersValue; + while (headersValue.size() != 4) { + headersValue.insert(QString::number(QRandomGenerator::global()->bounded(1, std::numeric_limits::max()))); + } + + auto headersValueList = headersValue.values(); + + QString defaultInitPacketMagicHeader = headersValueList.at(0); + QString defaultResponsePacketMagicHeader = headersValueList.at(1); + QString defaultUnderloadPacketMagicHeader = headersValueList.at(2); + QString defaultTransportPacketMagicHeader = headersValueList.at(3); + + containerConfig[config_key::junkPacketCount] = defaultJunkPacketCount; + containerConfig[config_key::junkPacketMinSize] = defaultJunkPacketMinSize; + containerConfig[config_key::junkPacketMaxSize] = defaultJunkPacketMaxSize; + containerConfig[config_key::initPacketJunkSize] = defaultInitPacketJunkSize; + containerConfig[config_key::responsePacketJunkSize] = defaultResponsePacketJunkSize; + containerConfig[config_key::initPacketMagicHeader] = defaultInitPacketMagicHeader; + containerConfig[config_key::responsePacketMagicHeader] = defaultResponsePacketMagicHeader; + containerConfig[config_key::underloadPacketMagicHeader] = defaultUnderloadPacketMagicHeader; + containerConfig[config_key::transportPacketMagicHeader] = defaultTransportPacketMagicHeader; + } + if (container == DockerContainer::Sftp) { containerConfig.insert(config_key::userName, protocols::sftp::defaultUserName); containerConfig.insert(config_key::password, Utils::getRandomString(10)); From 338499247dd64ac81c5eb8ac55192da3aad01fc0 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 19 Oct 2023 01:16:36 +0500 Subject: [PATCH 258/278] changed the display order of containers --- client/containers/containers_defs.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/containers/containers_defs.h b/client/containers/containers_defs.h index b9cb760d..92ca4f18 100644 --- a/client/containers/containers_defs.h +++ b/client/containers/containers_defs.h @@ -16,11 +16,11 @@ namespace amnezia Q_NAMESPACE enum DockerContainer { None = 0, - OpenVpn, - ShadowSocks, - Cloak, - WireGuard, Awg, + WireGuard, + OpenVpn, + Cloak, + ShadowSocks, Ipsec, // non-vpn From 366e27a321050119df3e1c8ca912a5bd113c7497 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Thu, 19 Oct 2023 09:27:39 +0800 Subject: [PATCH 259/278] re-adatped pagehome --- client/resources.qrc | 1 - .../ui/qml/Components/HomeRootMenuButton.qml | 140 --------- client/ui/qml/Controls2/Drawer2Type.qml | 279 +++++++++++++----- client/ui/qml/Pages2/PageHome.qml | 121 ++++++-- client/ui/qml/Pages2/PageStart.qml | 2 +- 5 files changed, 298 insertions(+), 245 deletions(-) delete mode 100644 client/ui/qml/Components/HomeRootMenuButton.qml diff --git a/client/resources.qrc b/client/resources.qrc index ac6e5a6d..d7f8ff7a 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -217,6 +217,5 @@ ui/qml/Controls2/TopCloseButtonType.qml images/controls/x-circle.svg ui/qml/Controls2/Drawer2Type.qml - ui/qml/Components/HomeRootMenuButton.qml diff --git a/client/ui/qml/Components/HomeRootMenuButton.qml b/client/ui/qml/Components/HomeRootMenuButton.qml deleted file mode 100644 index b2ca98dc..00000000 --- a/client/ui/qml/Components/HomeRootMenuButton.qml +++ /dev/null @@ -1,140 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -import "../Controls2/TextTypes" -import "../Controls2" - -Item { - id: root - - property string text - property int textMaximumLineCount: 2 - property int textElide: Qt.ElideRight - - property string descriptionText - - property var clickedFunction - - property string rightImageSource - - property string textColor: "#d7d8db" - property string descriptionColor: "#878B91" - property real textOpacity: 1.0 - - property string rightImageColor: "#d7d8db" - - property bool descriptionOnTop: false - - property string defaultServerHostName - property string defaultContainerName - - implicitWidth: content.implicitWidth + content.anchors.topMargin + content.anchors.bottomMargin - implicitHeight: content.implicitHeight + content.anchors.leftMargin + content.anchors.rightMargin - - ColumnLayout { - id: content - - anchors.right: parent.right - anchors.left: parent.left - anchors.bottom: parent.bottom - - RowLayout { - Layout.topMargin: 24 - Layout.leftMargin: 24 - Layout.rightMargin: 24 - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - - Header1TextType { - Layout.maximumWidth: root.width - 48 - 18 - 12 // todo - - maximumLineCount: 2 - elide: Qt.ElideRight - - text: root.text - Layout.alignment: Qt.AlignLeft - } - - - ImageButtonType { - id: rightImage - - hoverEnabled: false - image: rightImageSource - imageColor: rightImageColor - visible: rightImageSource ? true : false - -// implicitSize: 18 -// backGroudRadius: 5 - horizontalPadding: 0 - padding: 0 - spacing: 0 - - - Rectangle { - id: rightImageBackground - anchors.fill: parent - radius: 16 - color: "transparent" - - Behavior on color { - PropertyAnimation { duration: 200 } - } - } - } - } - - LabelTextType { - Layout.bottomMargin: 44 - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - - text: { - var description = "" - if (ServersModel.isDefaultServerHasWriteAccess()) { - if (SettingsController.isAmneziaDnsEnabled() - && ContainersModel.isAmneziaDnsContainerInstalled(ServersModel.getDefaultServerIndex())) { - description += "Amnezia DNS | " - } - } else { - if (ServersModel.isDefaultServerConfigContainsAmneziaDns()) { - description += "Amnezia DNS | " - } - } - - description += root.defaultContainerName + " | " + root.defaultServerHostName - return description - } - } - } - - - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - hoverEnabled: true - - onEntered: { - rightImageBackground.color = rightImage.hoveredColor - - root.textOpacity = 0.8 - } - - onExited: { - rightImageBackground.color = rightImage.defaultColor - - root.textOpacity = 1 - } - - onPressedChanged: { - rightImageBackground.color = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor - - root.textOpacity = 0.7 - } - - onClicked: { - if (clickedFunction && typeof clickedFunction === "function") { - clickedFunction() - } - } - } -} diff --git a/client/ui/qml/Controls2/Drawer2Type.qml b/client/ui/qml/Controls2/Drawer2Type.qml index b34390ac..fc486744 100644 --- a/client/ui/qml/Controls2/Drawer2Type.qml +++ b/client/ui/qml/Controls2/Drawer2Type.qml @@ -10,11 +10,18 @@ Item { target: PageController function onForceCloseDrawer() { - close() + if (root.opened()) { + close() + return + } + + if (root.expanded()) { + collapse() + } } } - signal closed + signal drawerClosed visible: false @@ -28,6 +35,13 @@ Item { property int contentHeight: 0 property Item contentParent: contentArea + property bool dragActive: dragArea.drag.active + + /** Initial height of button content */ + property int collapsedHeight: 0 + + property bool fullMouseAreaVisible: true + state: "closed" Rectangle { @@ -37,7 +51,7 @@ Item { height: parent.height width: parent.width radius: 16 - color: "#90000000" + color: "transparent" //"#90000000" border.color: "transparent" border.width: 1 visible: true @@ -45,90 +59,119 @@ Item { MouseArea { id: fullMouseArea anchors.fill: parent - enabled: (root.state === "opened") + enabled: (root.opened() || root.expanded()) hoverEnabled: true + visible: fullMouseAreaVisible onClicked: { - if (root.state === "opened") { + if (root.opened()) { close() return } + + if (root.expanded()) { + collapse() + } } + } + + Rectangle { + id: placeAreaHolder + height: (!root.opened()) ? 0 : parent.height - contentHeight + anchors.right: parent.right + anchors.left: parent.left + visible: true + color: "transparent" + + Drag.active: dragArea.drag.active + } + + + Rectangle { + id: contentArea + + anchors.top: placeAreaHolder.bottom + height: contentHeight + radius: 16 + color: root.defaultColor + border.width: 1 + border.color: root.borderColor + width: parent.width + visible: true Rectangle { - id: placeAreaHolder - height: parent.height - contentHeight + width: parent.radius + height: parent.radius + anchors.bottom: parent.bottom anchors.right: parent.right anchors.left: parent.left - visible: true - color: "transparent" - - Drag.active: dragArea.drag.active + color: parent.color } + MouseArea { + id: dragArea - Rectangle { - id: contentArea + anchors.fill: parent - anchors.top: placeAreaHolder.bottom - height: contentHeight - radius: 16 - color: root.defaultColor - border.width: 1 - border.color: root.borderColor - width: parent.width - visible: true + cursorShape: root.collapsed() ? Qt.PointingHandCursor : Qt.ArrowCursor // ? + hoverEnabled: true - Rectangle { - width: parent.radius - height: parent.radius - anchors.bottom: parent.bottom - anchors.right: parent.right - anchors.left: parent.left - color: parent.color - } + drag.target: placeAreaHolder + drag.axis: Drag.YAxis + drag.maximumY: (root.collapsed() || root.expanded()) ? (root.height - collapsedHeight) : root.height + drag.minimumY: (root.collapsed() || root.expanded()) ? (root.height - root.height * 0.9) : (root.height - root.height) - MouseArea { - id: dragArea - - anchors.fill: parent - - cursorShape: (root.state === "opened") ? Qt.PointingHandCursor : Qt.ArrowCursor - hoverEnabled: true - - drag.target: placeAreaHolder - drag.axis: Drag.YAxis - drag.maximumY: root.height - drag.minimumY: root.height - root.height - - /** If drag area is released at any point other than min or max y, transition to the other state */ - onReleased: { - if (root.state === "closed" && placeAreaHolder.y < root.height * 0.9) { - root.state = "opened" - return - } - - if (root.state === "opened" && placeAreaHolder.y > (root.height - root.height * 0.9)) { - close() - return - } - - placeAreaHolder.y = 0 + /** If drag area is released at any point other than min or max y, transition to the other state */ + onReleased: { + if (root.closed() && placeAreaHolder.y < root.height * 0.9) { + root.state = "opened" + return } - onClicked: { - if (root.state === "opened") { - close() - return - } + if (root.opened() && placeAreaHolder.y > (root.height - root.height * 0.9)) { + close() + return + } + + + if (root.collapsed() && placeAreaHolder.y < (root.height - collapsedHeight)) { + root.state = "expanded" + return + } + + if (root.expanded() && placeAreaHolder.y > (root.height - root.height * 0.9)) { + root.state = "collapsed" + return + } + + + if (root.opened()) { + placeAreaHolder.y = 0 + } + } + + onClicked: { + if (root.opened()) { + close() + return + } + + if (root.expanded()) { + collapse() + return + } + + if (root.collapsed()) { + root.state = "expanded" } } } } + } onStateChanged: { - if (root.state === "closed") { + if (root.closed() || root.collapsed()) { var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor() if (initialPageNavigationBarColor !== 0xFF1C1D21) { PageController.updateNavigationBarColor(initialPageNavigationBarColor) @@ -138,11 +181,12 @@ Item { PageController.drawerClose() } - closed() + drawerClosed() return } - if (root.state === "opened") { + + if (root.opened() || root.expanded()) { if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) { PageController.updateNavigationBarColor(0xFF1C1D21) } @@ -161,7 +205,7 @@ Item { name: "closed" PropertyChanges { target: placeAreaHolder - y: parent.height + y: root.height } }, @@ -171,6 +215,22 @@ Item { target: placeAreaHolder y: dragArea.drag.minimumY } + }, + + State { + name: "collapsed" + PropertyChanges { + target: placeAreaHolder + y: root.height - collapsedHeight + } + }, + + State { + name: "expanded" + PropertyChanges { + target: placeAreaHolder + y: root.height - root.height * 0.9 + } } ] @@ -199,6 +259,40 @@ Item { properties: "y" duration: 200 } + }, + + Transition { + from: "expanded" + to: "collapsed" + PropertyAnimation { + target: placeAreaHolder + properties: "y" + duration: 200 + } + + onRunningChanged: { + if (!running) { + draw2Background.color = "transparent" + fullMouseArea.visible = false + } + } + }, + + Transition { + from: "collapsed" + to: "expanded" + PropertyAnimation { + target: placeAreaHolder + properties: "y" + duration: 200 + } + + onRunningChanged: { + if (!running) { + draw2Background.color = "#90000000" + visibledMouseArea(true) + } + } } ] @@ -212,9 +306,11 @@ Item { } function open() { - if (root.visible && root.state !== "closed") { + //if (root.visible && !root.closed()) { + if (root.opened()) { return } + draw2Background.color = "#90000000" visibledMouseArea(true) @@ -240,21 +336,52 @@ Item { root.state = "closed" } - onVisibleChanged: { - // e.g cancel, ...... - if (!visible) { - if (root.state === "opened") { - if (needCloseButton) { - PageController.drawerClose() - } - } - - close() - } + function collapse() { + draw2Background.color = "transparent" + root.state = "collapsed" } + function visibledMouseArea(visbile) { fullMouseArea.visible = visbile dragArea.visible = visbile } + + function opened() { + return root.state === "opened" ? true : false + } + + function expanded() { + return root.state === "expanded" ? true : false + } + + function closed() { + return root.state === "closed" ? true : false + } + + function collapsed() { + return root.state === "collapsed" ? true : false + } + + + onVisibleChanged: { + // e.g cancel, ...... + if (!visible) { + if (root.opened()) { + if (needCloseButton) { + PageController.drawerClose() + } + + close() + } + + if (root.expanded()) { + if (needCloseButton) { + PageController.drawerClose() + } + + collapse() + } + } + } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index dafa3bdc..a3a3ed9d 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -30,13 +30,13 @@ PageType { target: PageController function onRestorePageHomeState(isContainerInstalled) { - menu.close() + buttonContent.collapse() if (isContainerInstalled) { containersDropDown.menuVisible = true } } function onForceCloseDrawer() { - menu.close() + buttonContent.collapse() } } @@ -69,7 +69,7 @@ PageType { } } - // collapsedServerMenuDescription.text = description + root.defaultContainerName + " | " + root.defaultServerHostName + collapsedServerMenuDescription.text = description + root.defaultContainerName + " | " + root.defaultServerHostName expandedServersMenuDescription.text = description + root.defaultServerHostName } @@ -79,7 +79,7 @@ PageType { Item { anchors.fill: parent - anchors.bottomMargin: defaultServerInfo.implicitHeight + anchors.bottomMargin: buttonContent.collapsedHeight ConnectButton { anchors.centerIn: parent @@ -88,7 +88,7 @@ PageType { Rectangle { id: buttonBackground - anchors.fill: defaultServerInfo + anchors { left: buttonContent.left; right: buttonContent.right; top: buttonContent.top } radius: 16 color: root.defaultColor @@ -105,41 +105,105 @@ PageType { } } - HomeRootMenuButton { - id: defaultServerInfo - anchors.right: parent.right - anchors.left: parent.left - anchors.bottom: parent.bottom - - text: root.defaultServerName - rightImageSource: "qrc:/images/controls/chevron-down.svg" - - defaultContainerName: root.defaultContainerName - defaultServerHostName: root.defaultServerHostName - - clickedFunction: function() { - menu.open() - } - } - Drawer2Type { - id: menu - parent: root + id: buttonContent + visible: true + + fullMouseAreaVisible: false + + /** True when expanded objects should be visible */ + property bool expandedVisibility: buttonContent.expanded() || (buttonContent.collapsed() && buttonContent.dragActive) + /** True when collapsed objects should be visible */ + property bool collapsedVisibility: buttonContent.collapsed() && !buttonContent.dragActive width: parent.width height: parent.height contentHeight: parent.height * 0.9 + ColumnLayout { + id: content + + parent: buttonContent.contentParent + + visible: buttonContent.collapsedVisibility + + anchors.right: parent.right + anchors.left: parent.left + anchors.top: parent.top + + onImplicitHeightChanged: { + if (buttonContent.collapsed() && buttonContent.collapsedHeight === 0) { + buttonContent.collapsedHeight = implicitHeight + } + } + + RowLayout { + Layout.topMargin: 24 + Layout.leftMargin: 24 + Layout.rightMargin: 24 + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + + Header1TextType { + id: collapsedButtonHeader + Layout.maximumWidth: root.width - 48 - 18 - 12 // todo + + maximumLineCount: 2 + elide: Qt.ElideRight + + text: root.defaultServerName + + Layout.alignment: Qt.AlignLeft + } + + + ImageButtonType { + id: collapsedButtonChevron + + hoverEnabled: false + image: "qrc:/images/controls/chevron-down.svg" + imageColor: "#d7d8db" + + horizontalPadding: 0 + padding: 0 + spacing: 0 + + Rectangle { + id: rightImageBackground + anchors.fill: parent + radius: 16 + color: "transparent" + + Behavior on color { + PropertyAnimation { duration: 200 } + } + } + } + } + + LabelTextType { + id: collapsedServerMenuDescription + Layout.bottomMargin: 44 + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + visible: buttonContent.collapsedVisibility + } + } + + Component.onCompleted: { + buttonContent.collapse() + } + ColumnLayout { id: serversMenuHeader - parent: menu.contentParent + parent: buttonContent.contentParent anchors.top: parent.top anchors.right: parent.right anchors.left: parent.left + visible: buttonContent.expandedVisibility + Header1TextType { Layout.fillWidth: true Layout.topMargin: 24 @@ -222,6 +286,7 @@ PageType { Layout.topMargin: 48 Layout.leftMargin: 16 Layout.rightMargin: 16 + visible: buttonContent.expandedVisibility headerText: qsTr("Servers") } @@ -230,7 +295,7 @@ PageType { Flickable { id: serversContainer - parent: menu.contentParent + parent: buttonContent.contentParent anchors.top: serversMenuHeader.bottom anchors.right: parent.right @@ -239,6 +304,8 @@ PageType { anchors.topMargin: 16 contentHeight: col.implicitHeight + visible: buttonContent.expandedVisibility + clip: true ScrollBar.vertical: ScrollBar { @@ -342,7 +409,7 @@ PageType { onClicked: function() { ServersModel.currentlyProcessedIndex = index PageController.goToPage(PageEnum.PageSettingsServerInfo) - menu.close() + buttonContent.collapse() } } } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 8afad392..4ccd40d8 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -247,7 +247,7 @@ PageType { z: 1 - onClosed: { + onDrawerClosed: { tabBar.setCurrentIndex(tabBar.previousIndex) } } From 6ec773079cef7fd8b39711801296f1120bdc0d06 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Thu, 19 Oct 2023 11:22:52 +0800 Subject: [PATCH 260/278] added hovering effect of button --- .../qml/Components/ShareConnectionDrawer.qml | 2 +- client/ui/qml/Controls2/Drawer2Type.qml | 32 +++++++++++++------ client/ui/qml/Pages2/PageHome.qml | 25 ++++++++++++++- 3 files changed, 48 insertions(+), 11 deletions(-) diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index ae0df0e2..03e7b500 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -31,7 +31,7 @@ Drawer2Type { height: parent.height contentHeight: parent.height * 0.9 - onClosed: { + onDrawerClosed: { configExtension = ".vpn" configCaption = qsTr("Save AmneziaVPN config") configFileName = "amnezia_config" diff --git a/client/ui/qml/Controls2/Drawer2Type.qml b/client/ui/qml/Controls2/Drawer2Type.qml index fc486744..f10fac21 100644 --- a/client/ui/qml/Controls2/Drawer2Type.qml +++ b/client/ui/qml/Controls2/Drawer2Type.qml @@ -22,6 +22,11 @@ Item { } signal drawerClosed + signal collapsedEntered + signal collapsedExited + signal collapsedEnter + signal collapsedPressChanged + visible: false @@ -41,6 +46,7 @@ Item { property int collapsedHeight: 0 property bool fullMouseAreaVisible: true + property MouseArea drawerDragArea: dragArea state: "closed" @@ -165,6 +171,18 @@ Item { root.state = "expanded" } } + + onExited: { + collapsedExited() + } + + onEntered: { + collapsedEnter() + } + + onPressedChanged: { + collapsedPressChanged() + } } } @@ -306,7 +324,6 @@ Item { } function open() { - //if (root.visible && !root.closed()) { if (root.opened()) { return } @@ -341,6 +358,11 @@ Item { root.state = "collapsed" } + function expand() { + draw2Background.color = "#90000000" + root.state = "expanded" + } + function visibledMouseArea(visbile) { fullMouseArea.visible = visbile @@ -374,14 +396,6 @@ Item { close() } - - if (root.expanded()) { - if (needCloseButton) { - PageController.drawerClose() - } - - collapse() - } } } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index a3a3ed9d..13148a80 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -122,7 +122,7 @@ PageType { ColumnLayout { - id: content + id: collapsedButtonContent parent: buttonContent.contentParent @@ -178,6 +178,12 @@ PageType { PropertyAnimation { duration: 200 } } } + + onClicked: { + if (buttonContent.collapsed()) { + buttonContent.expand() + } + } } } @@ -424,5 +430,22 @@ PageType { } } } + + onCollapsedEnter: { + collapsedButtonChevron.backgroundColor = collapsedButtonChevron.hoveredColor + collapsedButtonHeader.opacity = 0.8 + } + + onCollapsedExited: { + collapsedButtonChevron.backgroundColor = collapsedButtonChevron.defaultColor + collapsedButtonHeader.opacity = 1 + } + + onCollapsedPressChanged: { + collapsedButtonChevron.backgroundColor = buttonContent.drawerDragArea.pressed ? + collapsedButtonChevron.pressedColor : buttonContent.drawerDragArea.entered ? + collapsedButtonChevron.hoveredColor : collapsedButtonChevron.defaultColor + collapsedButtonHeader.opacity = 0.7 + } } } From f7bed04ab217baad98a72854c57578592718a812 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Thu, 19 Oct 2023 19:32:15 +0800 Subject: [PATCH 261/278] removed invalid function code --- client/ui/qml/Controls2/Drawer2Type.qml | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/client/ui/qml/Controls2/Drawer2Type.qml b/client/ui/qml/Controls2/Drawer2Type.qml index f10fac21..fd6406ee 100644 --- a/client/ui/qml/Controls2/Drawer2Type.qml +++ b/client/ui/qml/Controls2/Drawer2Type.qml @@ -34,6 +34,7 @@ Item { property string defaultColor: "#1C1D21" property string borderColor: "#2C2D30" + property string semitransparentColor: "#90000000" property bool needCollapsed: false @@ -57,7 +58,7 @@ Item { height: parent.height width: parent.width radius: 16 - color: "transparent" //"#90000000" + color: "transparent" border.color: "transparent" border.width: 1 visible: true @@ -264,7 +265,7 @@ Item { onRunningChanged: { if (!running) { - visibledMouseArea(false) + fullMouseArea.visible = false } } }, @@ -307,8 +308,8 @@ Item { onRunningChanged: { if (!running) { - draw2Background.color = "#90000000" - visibledMouseArea(true) + draw2Background.color = semitransparentColor + fullMouseArea.visible = true } } } @@ -328,9 +329,8 @@ Item { return } - draw2Background.color = "#90000000" - - visibledMouseArea(true) + draw2Background.color = semitransparentColor + fullMouseArea.visible = true root.y = 0 root.state = "opened" @@ -359,16 +359,10 @@ Item { } function expand() { - draw2Background.color = "#90000000" + draw2Background.color = semitransparentColor root.state = "expanded" } - - function visibledMouseArea(visbile) { - fullMouseArea.visible = visbile - dragArea.visible = visbile - } - function opened() { return root.state === "opened" ? true : false } From a6949bd3aefeb3e90852d85be5a5028d0aff4b50 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Thu, 19 Oct 2023 19:45:22 +0800 Subject: [PATCH 262/278] resized questiondrawer of page serverdata --- client/ui/qml/Pages2/PageSettingsServerData.qml | 6 ++++-- client/ui/qml/Pages2/PageSettingsServerInfo.qml | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 2e401c67..09066ccb 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -14,6 +14,8 @@ import "../Components" PageType { id: root + property Item questionDrawerParent + Connections { target: InstallController @@ -193,9 +195,9 @@ PageType { QuestionDrawer { id: questionDrawer - drawerHeight: 0.8 + drawerHeight: 0.5 - parent: root + parent: questionDrawerParent } } } diff --git a/client/ui/qml/Pages2/PageSettingsServerInfo.qml b/client/ui/qml/Pages2/PageSettingsServerInfo.qml index f6d569dd..e14c6ab5 100644 --- a/client/ui/qml/Pages2/PageSettingsServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsServerInfo.qml @@ -169,6 +169,7 @@ PageType { } PageSettingsServerData { stackView: root.stackView + questionDrawerParent: root } } } From 6c78b4ec8f8bad64ee2f936096dd421c47192af6 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Thu, 19 Oct 2023 23:01:03 +0800 Subject: [PATCH 263/278] enabled drag-pagehome-drawer in tabBar --- client/ui/qml/Pages2/PageStart.qml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 4ccd40d8..12c83e06 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -176,6 +176,12 @@ PageType { strokeColor: "#2C2D30" fillColor: "#1C1D21" } + + MouseArea { + id: noPropagateMouseEvent + anchors.fill: parent + enabled: true + } } TabImageButtonType { From 2da1025f2695c8a8ba2b1f0d82bdcacd0437b473 Mon Sep 17 00:00:00 2001 From: pokamest Date: Fri, 20 Oct 2023 02:25:40 +0100 Subject: [PATCH 264/278] Random port on install --- client/protocols/protocols_defs.cpp | 25 +++++++++--- client/protocols/protocols_defs.h | 2 + client/ui/controllers/installController.cpp | 40 ++++++++++--------- .../PageSetupWizardProtocolSettings.qml | 2 +- 4 files changed, 44 insertions(+), 25 deletions(-) diff --git a/client/protocols/protocols_defs.cpp b/client/protocols/protocols_defs.cpp index b3823a11..a451014c 100644 --- a/client/protocols/protocols_defs.cpp +++ b/client/protocols/protocols_defs.cpp @@ -1,5 +1,7 @@ #include "protocols_defs.h" +#include + using namespace amnezia; QDebug operator<<(QDebug debug, const amnezia::ProtocolEnumNS::Proto &p) @@ -98,15 +100,28 @@ amnezia::ServiceType ProtocolProps::protocolService(Proto p) } } +int ProtocolProps::getPortForInstall(Proto p) +{ + switch (p) { + case Awg: + case WireGuard: + case ShadowSocks: + case OpenVpn: + return QRandomGenerator::global()->bounded(30000, 50000); + default: + return defaultPort(p); + } +} + int ProtocolProps::defaultPort(Proto p) { switch (p) { case Proto::Any: return -1; - case Proto::OpenVpn: return 1194; - case Proto::Cloak: return 443; - case Proto::ShadowSocks: return 6789; - case Proto::WireGuard: return 51820; - case Proto::Awg: return 55424; + case Proto::OpenVpn: return QString(protocols::openvpn::defaultPort).toInt(); + case Proto::Cloak: return QString(protocols::cloak::defaultPort).toInt(); + case Proto::ShadowSocks: return QString(protocols::shadowsocks::defaultPort).toInt(); + case Proto::WireGuard: return QString(protocols::wireguard::defaultPort).toInt(); + case Proto::Awg: return QString(protocols::awg::defaultPort).toInt(); case Proto::Ikev2: return -1; case Proto::L2tp: return -1; diff --git a/client/protocols/protocols_defs.h b/client/protocols/protocols_defs.h index ed2ed313..ab9cac1b 100644 --- a/client/protocols/protocols_defs.h +++ b/client/protocols/protocols_defs.h @@ -228,6 +228,8 @@ namespace amnezia Q_INVOKABLE static ServiceType protocolService(Proto p); + Q_INVOKABLE static int getPortForInstall(Proto p); + Q_INVOKABLE static int defaultPort(Proto p); Q_INVOKABLE static bool defaultPortChangeable(Proto p); diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 8efe368b..8853f108 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -75,33 +75,35 @@ void InstallController::install(DockerContainer container, int port, TransportPr ProtocolProps::transportProtoToString(transportProto, protocol)); if (container == DockerContainer::Awg) { - QString defaultJunkPacketCount = QString::number(QRandomGenerator::global()->bounded(3, 10)); - QString defaultJunkPacketMinSize = QString::number(50); - QString defaultJunkPacketMaxSize = QString::number(1000); - QString defaultInitPacketJunkSize = QString::number(QRandomGenerator::global()->bounded(15, 150)); - QString defaultResponsePacketJunkSize = QString::number(QRandomGenerator::global()->bounded(15, 150)); + QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(3, 10)); + QString junkPacketMinSize = QString::number(50); + QString junkPacketMaxSize = QString::number(1000); + QString initPacketJunkSize = QString::number(QRandomGenerator::global()->bounded(15, 150)); + QString responsePacketJunkSize = QString::number(QRandomGenerator::global()->bounded(15, 150)); QSet headersValue; while (headersValue.size() != 4) { - headersValue.insert(QString::number(QRandomGenerator::global()->bounded(1, std::numeric_limits::max()))); + + auto max = (std::numeric_limits::max)(); + headersValue.insert(QString::number(QRandomGenerator::global()->bounded(1, max))); } auto headersValueList = headersValue.values(); - QString defaultInitPacketMagicHeader = headersValueList.at(0); - QString defaultResponsePacketMagicHeader = headersValueList.at(1); - QString defaultUnderloadPacketMagicHeader = headersValueList.at(2); - QString defaultTransportPacketMagicHeader = headersValueList.at(3); + QString initPacketMagicHeader = headersValueList.at(0); + QString responsePacketMagicHeader = headersValueList.at(1); + QString underloadPacketMagicHeader = headersValueList.at(2); + QString transportPacketMagicHeader = headersValueList.at(3); - containerConfig[config_key::junkPacketCount] = defaultJunkPacketCount; - containerConfig[config_key::junkPacketMinSize] = defaultJunkPacketMinSize; - containerConfig[config_key::junkPacketMaxSize] = defaultJunkPacketMaxSize; - containerConfig[config_key::initPacketJunkSize] = defaultInitPacketJunkSize; - containerConfig[config_key::responsePacketJunkSize] = defaultResponsePacketJunkSize; - containerConfig[config_key::initPacketMagicHeader] = defaultInitPacketMagicHeader; - containerConfig[config_key::responsePacketMagicHeader] = defaultResponsePacketMagicHeader; - containerConfig[config_key::underloadPacketMagicHeader] = defaultUnderloadPacketMagicHeader; - containerConfig[config_key::transportPacketMagicHeader] = defaultTransportPacketMagicHeader; + containerConfig[config_key::junkPacketCount] = junkPacketCount; + containerConfig[config_key::junkPacketMinSize] = junkPacketMinSize; + containerConfig[config_key::junkPacketMaxSize] = junkPacketMaxSize; + containerConfig[config_key::initPacketJunkSize] = initPacketJunkSize; + containerConfig[config_key::responsePacketJunkSize] = responsePacketJunkSize; + containerConfig[config_key::initPacketMagicHeader] = initPacketMagicHeader; + containerConfig[config_key::responsePacketMagicHeader] = responsePacketMagicHeader; + containerConfig[config_key::underloadPacketMagicHeader] = underloadPacketMagicHeader; + containerConfig[config_key::transportPacketMagicHeader] = transportPacketMagicHeader; } if (container == DockerContainer::Sftp) { diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 2b97f044..7698c755 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -224,7 +224,7 @@ PageType { if (ProtocolProps.defaultPort(defaultContainerProto) < 0) { port.visible = false } else { - port.textFieldText = ProtocolProps.defaultPort(defaultContainerProto) + port.textFieldText = ProtocolProps.getPortForInstall(defaultContainerProto) } transportProtoSelector.currentIndex = ProtocolProps.defaultTransportProto(defaultContainerProto) From 0a15f44193fb7be45dd57e3a19babfb295175a95 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Fri, 20 Oct 2023 10:38:12 +0800 Subject: [PATCH 265/278] removed states 'opened', 'closed' --- client/ui/qml/Controls2/Drawer2Type.qml | 131 +++++------------------- 1 file changed, 25 insertions(+), 106 deletions(-) diff --git a/client/ui/qml/Controls2/Drawer2Type.qml b/client/ui/qml/Controls2/Drawer2Type.qml index fd6406ee..fa393982 100644 --- a/client/ui/qml/Controls2/Drawer2Type.qml +++ b/client/ui/qml/Controls2/Drawer2Type.qml @@ -10,11 +10,6 @@ Item { target: PageController function onForceCloseDrawer() { - if (root.opened()) { - close() - return - } - if (root.expanded()) { collapse() } @@ -43,13 +38,12 @@ Item { property bool dragActive: dragArea.drag.active - /** Initial height of button content */ property int collapsedHeight: 0 property bool fullMouseAreaVisible: true property MouseArea drawerDragArea: dragArea - state: "closed" + state: "collapsed" Rectangle { id: draw2Background @@ -66,16 +60,11 @@ Item { MouseArea { id: fullMouseArea anchors.fill: parent - enabled: (root.opened() || root.expanded()) + enabled: root.expanded() hoverEnabled: true visible: fullMouseAreaVisible onClicked: { - if (root.opened()) { - close() - return - } - if (root.expanded()) { collapse() } @@ -84,7 +73,10 @@ Item { Rectangle { id: placeAreaHolder - height: (!root.opened()) ? 0 : parent.height - contentHeight + + // for apdating home drawer, normal drawer will reset it + height: 0 + anchors.right: parent.right anchors.left: parent.left visible: true @@ -120,49 +112,27 @@ Item { anchors.fill: parent - cursorShape: root.collapsed() ? Qt.PointingHandCursor : Qt.ArrowCursor // ? + cursorShape: root.collapsed() ? Qt.PointingHandCursor : Qt.ArrowCursor hoverEnabled: true drag.target: placeAreaHolder drag.axis: Drag.YAxis - drag.maximumY: (root.collapsed() || root.expanded()) ? (root.height - collapsedHeight) : root.height - drag.minimumY: (root.collapsed() || root.expanded()) ? (root.height - root.height * 0.9) : (root.height - root.height) + drag.maximumY: root.height - root.collapsedHeight + drag.minimumY: root.collapsedHeight > 0 ? root.height - root.height * 0.9 : 0 /** If drag area is released at any point other than min or max y, transition to the other state */ onReleased: { - if (root.closed() && placeAreaHolder.y < root.height * 0.9) { - root.state = "opened" - return - } - - if (root.opened() && placeAreaHolder.y > (root.height - root.height * 0.9)) { - close() - return - } - - - if (root.collapsed() && placeAreaHolder.y < (root.height - collapsedHeight)) { + if (root.collapsed() && placeAreaHolder.y < drag.maximumY) { root.state = "expanded" return } - - if (root.expanded() && placeAreaHolder.y > (root.height - root.height * 0.9)) { + if (root.expanded() && placeAreaHolder.y > drag.minimumY) { root.state = "collapsed" return } - - - if (root.opened()) { - placeAreaHolder.y = 0 - } } onClicked: { - if (root.opened()) { - close() - return - } - if (root.expanded()) { collapse() return @@ -186,11 +156,10 @@ Item { } } } - } onStateChanged: { - if (root.closed() || root.collapsed()) { + if (root.collapsed()) { var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor() if (initialPageNavigationBarColor !== 0xFF1C1D21) { PageController.updateNavigationBarColor(initialPageNavigationBarColor) @@ -205,7 +174,7 @@ Item { return } - if (root.opened() || root.expanded()) { + if (root.expanded()) { if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) { PageController.updateNavigationBarColor(0xFF1C1D21) } @@ -220,27 +189,11 @@ Item { /** Two states of buttonContent, great place to add any future animations for the drawer */ states: [ - State { - name: "closed" - PropertyChanges { - target: placeAreaHolder - y: root.height - } - }, - - State { - name: "opened" - PropertyChanges { - target: placeAreaHolder - y: dragArea.drag.minimumY - } - }, - State { name: "collapsed" PropertyChanges { target: placeAreaHolder - y: root.height - collapsedHeight + y: dragArea.drag.maximumY } }, @@ -248,38 +201,12 @@ Item { name: "expanded" PropertyChanges { target: placeAreaHolder - y: root.height - root.height * 0.9 + y: dragArea.drag.minimumY } } ] transitions: [ - Transition { - from: "opened" - to: "closed" - PropertyAnimation { - target: placeAreaHolder - properties: "y" - duration: 200 - } - - onRunningChanged: { - if (!running) { - fullMouseArea.visible = false - } - } - }, - - Transition { - from: "closed" - to: "opened" - PropertyAnimation { - target: placeAreaHolder - properties: "y" - duration: 200 - } - }, - Transition { from: "expanded" to: "collapsed" @@ -324,33 +251,32 @@ Item { duration: 200 } + // for normal drawer function open() { - if (root.opened()) { + if (root.expanded()) { return } draw2Background.color = semitransparentColor fullMouseArea.visible = true + collapsedHeight = 0 + root.y = 0 - root.state = "opened" + root.state = "expanded" root.visible = true root.height = parent.height contentArea.height = contentHeight - placeAreaHolder.height = root.height - contentHeight - placeAreaHolder.y = root.height - root.height - - dragArea.drag.maximumY = root.height - dragArea.drag.minimumY = 0 + placeAreaHolder.y = 0 + placeAreaHolder.height = root.height - contentHeight animationVisible.running = true } function close() { - draw2Background.color = "transparent" - root.state = "closed" + collapse() } function collapse() { @@ -358,23 +284,16 @@ Item { root.state = "collapsed" } + // for page home function expand() { draw2Background.color = semitransparentColor root.state = "expanded" } - function opened() { - return root.state === "opened" ? true : false - } - function expanded() { return root.state === "expanded" ? true : false } - function closed() { - return root.state === "closed" ? true : false - } - function collapsed() { return root.state === "collapsed" ? true : false } @@ -383,7 +302,7 @@ Item { onVisibleChanged: { // e.g cancel, ...... if (!visible) { - if (root.opened()) { + if (root.expanded()) { if (needCloseButton) { PageController.drawerClose() } From 58ad7dc16135a8cbe3fcb1ef55a1406dbdd191dc Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 20 Oct 2023 14:10:04 +0500 Subject: [PATCH 266/278] removed the "remove protocol" buttons from where they shouldn't be --- client/ui/qml/Controls2/SwitcherType.qml | 11 ++++------- client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml | 5 +++++ client/ui/qml/Pages2/PageProtocolRaw.qml | 2 ++ 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/client/ui/qml/Controls2/SwitcherType.qml b/client/ui/qml/Controls2/SwitcherType.qml index ee7372f5..1dbd0e84 100644 --- a/client/ui/qml/Controls2/SwitcherType.qml +++ b/client/ui/qml/Controls2/SwitcherType.qml @@ -30,17 +30,13 @@ Switch { property string hoveredIndicatorBackgroundColor: Qt.rgba(1, 1, 1, 0.08) property string defaultIndicatorBackgroundColor: "transparent" - implicitWidth: content.implicitWidth + switcher.implicitWidth - implicitHeight: content.implicitHeight - hoverEnabled: enabled ? true : false indicator: Rectangle { id: switcher - anchors.left: content.right + anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 4 implicitWidth: 52 implicitHeight: 32 @@ -90,11 +86,11 @@ Switch { contentItem: ColumnLayout { id: content - anchors.fill: parent - anchors.rightMargin: switcher.implicitWidth + anchors.verticalCenter: parent.verticalCenter ListItemTitleType { Layout.fillWidth: true + rightPadding: indicator.width text: root.text color: root.enabled ? root.textColor : root.textDisabledColor @@ -104,6 +100,7 @@ Switch { id: description Layout.fillWidth: true + rightPadding: indicator.width color: root.enabled ? root.descriptionTextColor : root.descriptionTextDisabledColor diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index c8469dcb..cd2dd51d 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -5,6 +5,7 @@ import QtQuick.Layouts import SortFilterProxyModel 0.2 import PageEnum 1.0 +import ContainerEnum 1.0 import "./" import "../Controls2" @@ -252,6 +253,8 @@ PageType { ColumnLayout { id: checkboxLayout + + anchors.fill: parent CheckBoxType { Layout.fillWidth: true @@ -351,6 +354,8 @@ PageType { Layout.leftMargin: -8 implicitHeight: 32 + visible: ContainersModel.getCurrentlyProcessedContainerIndex() === ContainerEnum.OpenVpn + defaultColor: "transparent" hoveredColor: Qt.rgba(1, 1, 1, 0.08) pressedColor: Qt.rgba(1, 1, 1, 0.12) diff --git a/client/ui/qml/Pages2/PageProtocolRaw.qml b/client/ui/qml/Pages2/PageProtocolRaw.qml index f0959143..f3621d96 100644 --- a/client/ui/qml/Pages2/PageProtocolRaw.qml +++ b/client/ui/qml/Pages2/PageProtocolRaw.qml @@ -169,6 +169,8 @@ PageType { width: parent.width + visible: ServersModel.isCurrentlyProcessedServerHasWriteAccess() + text: qsTr("Remove ") + ContainersModel.getCurrentlyProcessedContainerName() textColor: "#EB5757" From da1cdfd6faef78dfbc566c79961b00de0cebbbd2 Mon Sep 17 00:00:00 2001 From: ronoaer Date: Fri, 20 Oct 2023 18:01:57 +0800 Subject: [PATCH 267/278] translated new source strings to chinese --- client/translations/amneziavpn_zh_CN.ts | 40 ++++++++++--------------- 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index 64faa6f4..76a4feac 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -131,7 +131,7 @@ Unable change protocol while there is an active connection - Невозможно изменить протокол при наличии активного соединения + 已建立连接时无法更改服务器配置 @@ -162,47 +162,47 @@ 已安装在服务器上 - - + + %1 installed successfully. %1 安装成功。 - - + + %1 is already installed on the server. 服务器上已经安装 %1。 - + Added containers that were already installed on the server 添加已安装在服务器上的容器 - + Already installed containers were found on the server. All installed containers have been added to the application 在服务上发现已经安装协议并添加至应用 - + Settings updated successfully 配置更新成功 - + Server '%1' was removed 已移除服务器 '%1' - + All containers from server '%1' have been removed 服务器 '%1' 的所有容器已移除 - + %1 has been removed from the server '%2' %1 已从服务器 '%2' 上移除 @@ -223,12 +223,12 @@ Already installed containers were found on the server. All installed containers 协议已从 - + Please login as the user 请以用户身份登录 - + Server added successfully 增加服务器成功 @@ -308,7 +308,7 @@ Already installed containers were found on the server. All installed containers Unable change server while there is an active connection - Невозможно изменить сервер при наличии активного соединения + 已建立连接时无法更改服务器配置 @@ -2335,7 +2335,7 @@ and will not be shared or disclosed to the Amnezia or any third parties QObject - + Sftp service Sftp 服务 @@ -2839,16 +2839,6 @@ While it offers a blend of security, stability, and speed, it's essential t error 0x%1: %2 错误 0x%1: %2 - - - WireGuard Configuration Highlighter - - - - - &Randomize colors - - SelectLanguageDrawer From 3d60ac751eefa101bbeb8ae5fcf3bb1cd57a969f Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Fri, 20 Oct 2023 20:52:14 +0500 Subject: [PATCH 268/278] removed the default protocol/server change if connected to VPN --- client/translations/amneziavpn_ru.ts | 130 +++++++++--------- client/translations/amneziavpn_zh_CN.ts | 130 +++++++++--------- client/ui/controllers/importController.cpp | 2 - client/ui/controllers/installController.cpp | 5 - client/ui/models/containers_model.cpp | 9 +- client/ui/models/containers_model.h | 1 + .../qml/Pages2/PageSetupWizardInstalling.qml | 8 ++ .../qml/Pages2/PageSetupWizardViewConfig.qml | 4 + 8 files changed, 151 insertions(+), 138 deletions(-) diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index 733c374d..8c72f093 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -135,7 +135,7 @@ ImportController - + Scanned %1 of %2. Отсканировано %1 из%2. @@ -144,13 +144,13 @@ InstallController - + %1 installed successfully. %1 успешно установлен. - + %1 is already installed on the server. %1 уже установлен на сервер. @@ -162,39 +162,39 @@ Added containers that were already installed on the server В приложение добавлены обнаруженные на сервере протоклы и сервисы - + Already installed containers were found on the server. All installed containers have been added to the application На сервере обнаружены установленные протоколы и сервисы, все они добавлены в приложение - + Settings updated successfully Настройки успешно обновлены - + Server '%1' was removed Сервер '%1' был удален - + All containers from server '%1' have been removed Все протоклы и сервисы были удалены с сервера '%1' - + %1 has been removed from the server '%2' %1 был удален с сервера '%2' - + Please login as the user Пожалуйста, войдите в систему от имени пользователя - + Server added successfully Сервер успешно добавлен @@ -397,195 +397,195 @@ Already installed containers were found on the server. All installed containers PageProtocolOpenVpnSettings - + OpenVPN settings Настройки OpenVPN - + VPN Addresses Subnet VPN Адреса Подсеть - + Network protocol Сетевой протокол - + Port Порт - + Auto-negotiate encryption Шифрование с автоматическим согласованием - + Hash Хэш - + SHA512 SHA512 - + SHA384 SHA384 - + SHA256 SHA256 - + SHA3-512 SHA3-512 - + SHA3-384 SHA3-384 - + SHA3-256 SHA3-256 - + whirlpool whirlpool - + BLAKE2b512 BLAKE2b512 - + BLAKE2s256 BLAKE2s256 - + SHA1 SHA1 - + Cipher Шифрование - + AES-256-GCM AES-256-GCM - + AES-192-GCM AES-192-GCM - + AES-128-GCM AES-128-GCM - + AES-256-CBC AES-256-CBC - + AES-192-CBC AES-192-CBC - + AES-128-CBC AES-128-CBC - + ChaCha20-Poly1305 ChaCha20-Poly1305 - + ARIA-256-CBC ARIA-256-CBC - + CAMELLIA-256-CBC CAMELLIA-256-CBC - + none none - + TLS auth TLS авторизация - + Block DNS requests outside of VPN Блокировать DNS запросы за пределами VPN - + Additional client configuration commands Дополнительные команды конфигурации клиента - - + + Commands: Commands: - + Additional server configuration commands Дополнительные команды конфигурации сервера - + Remove OpenVPN Удалить OpenVPN - + Remove OpenVpn from server? Удалить OpenVpn с сервера? - + All users who you shared a connection with will no longer be able to connect to it. Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. - + Continue Продолжить - + Cancel Отменить - + Save and Restart Amnezia Сохранить и перезагрузить @@ -608,27 +608,27 @@ Already installed containers were found on the server. All installed containers Параметры подключения %1 - + Remove Удалить - + Remove %1 from server? Удалить %1 с сервера? - + All users who you shared a connection with will no longer be able to connect to it. Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. - + Continue Продолжить - + Cancel Отменить @@ -1694,7 +1694,7 @@ and will not be shared or disclosed to the Amnezia or any third parties PageSetupWizardInstalling - + The server has already been added to the application Сервер уже был добавлен в приложение @@ -1707,28 +1707,28 @@ and will not be shared or disclosed to the Amnezia or any third parties занят установкой других протоколов или сервисов. Установка Amnesia - + Amnezia has detected that your server is currently Amnezia обнаружила, что ваш сервер в настоящее время - + busy installing other software. Amnezia installation занят установкой другого программного обеспечения. Установка Amnezia - + will pause until the server finishes installing other software будет приостановлена до тех пор, пока сервер не завершит установку - + Installing Установка - + Usually it takes no more than 5 minutes Обычно это занимает не более 5 минут @@ -1846,27 +1846,27 @@ and will not be shared or disclosed to the Amnezia or any third parties PageSetupWizardViewConfig - + New connection Новое соединение - + Do not use connection code from public sources. It could be created to intercept your data. Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватывать ваши данные. - + Collapse content Свернуть - + Show content Показать содержимое ключа - + Connect Подключиться diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index 64faa6f4..10064ce2 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -146,7 +146,7 @@ ImportController - + Scanned %1 of %2. 扫描 %1 of %2. @@ -163,13 +163,13 @@ - + %1 installed successfully. %1 安装成功。 - + %1 is already installed on the server. 服务器上已经安装 %1。 @@ -180,29 +180,29 @@ Added containers that were already installed on the server 添加已安装在服务器上的容器 - + Already installed containers were found on the server. All installed containers have been added to the application 在服务上发现已经安装协议并添加至应用 - + Settings updated successfully 配置更新成功 - + Server '%1' was removed 已移除服务器 '%1' - + All containers from server '%1' have been removed 服务器 '%1' 的所有容器已移除 - + %1 has been removed from the server '%2' %1 已从服务器 '%2' 上移除 @@ -223,12 +223,12 @@ Already installed containers were found on the server. All installed containers 协议已从 - + Please login as the user 请以用户身份登录 - + Server added successfully 增加服务器成功 @@ -431,180 +431,180 @@ Already installed containers were found on the server. All installed containers PageProtocolOpenVpnSettings - + OpenVPN settings OpenVPN 配置 - + VPN Addresses Subnet VPN子网掩码 - + Network protocol 网络协议 - + Port 端口 - + Auto-negotiate encryption 自定义加密方式 - + Hash - + SHA512 - + SHA384 - + SHA256 - + SHA3-512 - + SHA3-384 - + SHA3-256 - + whirlpool - + BLAKE2b512 - + BLAKE2s256 - + SHA1 - + Cipher - + AES-256-GCM - + AES-192-GCM - + AES-128-GCM - + AES-256-CBC - + AES-192-CBC - + AES-128-CBC - + ChaCha20-Poly1305 - + ARIA-256-CBC - + CAMELLIA-256-CBC - + none - + TLS auth TLS认证 - + Block DNS requests outside of VPN 阻止VPN外的DNS请求 - + Additional client configuration commands 附加客户端配置命令 - - + + Commands: 命令: - + Additional server configuration commands 附加服务器端配置命令 - + Remove OpenVPN 移除OpenVPN - + Remove OpenVpn from server? 从服务器移除OpenVPN吗? - + All users who you shared a connection with will no longer be able to connect to it. 使用此共享连接的所有用户,将无法再连接它。 @@ -613,17 +613,17 @@ Already installed containers were found on the server. All installed containers 与您共享连接的所有用户将无法再连接到此链接 - + Continue 继续 - + Cancel 取消 - + Save and Restart Amnezia 保存并重启Amnezia @@ -650,17 +650,17 @@ Already installed containers were found on the server. All installed containers %1 连接选项 - + Remove 移除 - + Remove %1 from server? 从服务器移除 %1 ? - + All users who you shared a connection with will no longer be able to connect to it. 使用此共享连接的所有用户,将无法再连接它。 @@ -673,12 +673,12 @@ Already installed containers were found on the server. All installed containers 与您共享连接的所有用户将无法再连接到此链接 - + Continue 继续 - + Cancel 取消 @@ -1801,22 +1801,22 @@ and will not be shared or disclosed to the Amnezia or any third parties PageSetupWizardInstalling - + Usually it takes no more than 5 minutes 通常不超过5分钟 - + The server has already been added to the application 服务器已添加到应用软件中 - + Amnezia has detected that your server is currently Amnezia 检测到您的服务器当前 - + busy installing other software. Amnezia installation 正安装其他软件。Amnezia安装 @@ -1829,12 +1829,12 @@ and will not be shared or disclosed to the Amnezia or any third parties 正安装其他软件。Amnezia安装 - + will pause until the server finishes installing other software 将暂停,直到其他软件安装完成。 - + Installing 安装中 @@ -1952,27 +1952,27 @@ and will not be shared or disclosed to the Amnezia or any third parties PageSetupWizardViewConfig - + New connection 新连接 - + Do not use connection code from public sources. It could be created to intercept your data. 请勿使用公共来源的连接码。它可以被创建来拦截您的数据。 - + Collapse content 折叠内容 - + Show content 显示内容 - + Connect 连接 diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index 08b662ec..bd924087 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -144,8 +144,6 @@ void ImportController::importConfig() if (credentials.isValid() || m_config.contains(config_key::containers)) { m_serversModel->addServer(m_config); - m_serversModel->setDefaultServerIndex(m_serversModel->getServersCount() - 1); - emit importFinished(); } else { qDebug() << "Failed to import profile"; diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 8efe368b..2aa4e81b 100644 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -163,7 +163,6 @@ void InstallController::installServer(DockerContainer container, QJsonObject &co server.insert(config_key::defaultContainer, ContainerProps::containerToString(container)); m_serversModel->addServer(server); - m_serversModel->setDefaultServerIndex(m_serversModel->getServersCount() - 1); emit installServerFinished(finishMessage); return; @@ -214,9 +213,6 @@ void InstallController::installContainer(DockerContainer container, QJsonObject "All installed containers have been added to the application"); } - if (ContainerProps::containerService(container) == ServiceType::Vpn) { - m_containersModel->setData(m_containersModel->index(container), true, ContainersModel::Roles::IsDefaultRole); - } emit installContainerFinished(finishMessage, ContainerProps::containerService(container) == ServiceType::Other); return; } @@ -509,7 +505,6 @@ void InstallController::addEmptyServer() server.insert(config_key::defaultContainer, ContainerProps::containerToString(DockerContainer::None)); m_serversModel->addServer(server); - m_serversModel->setDefaultServerIndex(m_serversModel->getServersCount() - 1); emit installServerFinished(tr("Server added successfully")); } diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index 0c99041e..780809c5 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -41,7 +41,7 @@ bool ContainersModel::setData(const QModelIndex &index, const QVariant &value, i // return container; case IsInstalledRole: // return m_settings->containers(m_currentlyProcessedServerIndex).contains(container); - case IsDefaultRole: { + case IsDefaultRole: { //todo remove m_settings->setDefaultContainer(m_currentlyProcessedServerIndex, container); m_defaultContainerIndex = container; emit defaultContainerChanged(); @@ -117,6 +117,13 @@ QString ContainersModel::getDefaultContainerName() return ContainerProps::containerHumanNames().value(m_defaultContainerIndex); } +void ContainersModel::setDefaultContainer(DockerContainer container) +{ + m_settings->setDefaultContainer(m_currentlyProcessedServerIndex, container); + m_defaultContainerIndex = container; + emit defaultContainerChanged(); +} + int ContainersModel::getCurrentlyProcessedContainerIndex() { return m_currentlyProcessedContainerIndex; diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index cc549bb3..4ba85749 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -46,6 +46,7 @@ public: public slots: DockerContainer getDefaultContainer(); QString getDefaultContainerName(); + void setDefaultContainer(DockerContainer container); void setCurrentlyProcessedServerIndex(const int index); diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index bd1fd7e6..50aad294 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -24,6 +24,10 @@ PageType { target: InstallController function onInstallContainerFinished(finishedMessage, isServiceInstall) { + if (!ConnectionController.isConnected && !isServiceInstall) { + ContainersModel.setDefaultContainer(ContainersModel.getCurrentlyProcessedContainerIndex) + } + PageController.goToStartPage() if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageHome)) { PageController.restorePageHomeState(true) @@ -41,6 +45,10 @@ PageType { } function onInstallServerFinished(finishedMessage) { + if (!ConnectionController.isConnected) { + ServersModel.setDefaultServerIndex(ServersModel.getServersCount() - 1); + } + PageController.goToStartPage() if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSetupWizardStart)) { PageController.replaceStartPage() diff --git a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml index 83132bd9..ac35651f 100644 --- a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml +++ b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml @@ -24,6 +24,10 @@ PageType { } function onImportFinished() { + if (ConnectionController.isConnected) { + ServersModel.setDefaultServerIndex(ServersModel.getServersCount() - 1); + } + PageController.goToStartPage() if (stackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageSetupWizardStart)) { PageController.replaceStartPage() From 09305724fa35b3524d527b170284a2d1d367e527 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Fri, 20 Oct 2023 16:44:30 -0400 Subject: [PATCH 269/278] Fix MTU len for Win WG/AWG --- CMakeLists.txt | 2 +- client/3rd-prebuilt | 2 +- client/android/build.gradle | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 68de51fb..12fc8dce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.0.8.4 +project(${PROJECT} VERSION 4.0.8.5 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) diff --git a/client/3rd-prebuilt b/client/3rd-prebuilt index 15b0ff39..ac32d335 160000 --- a/client/3rd-prebuilt +++ b/client/3rd-prebuilt @@ -1 +1 @@ -Subproject commit 15b0ff395d9d372339c5ea8ea35cb2715b975ea9 +Subproject commit ac32d33555bd62f0b0af314b1e5119d6d78a1a4e diff --git a/client/android/build.gradle b/client/android/build.gradle index 49e378a0..a6b3f651 100644 --- a/client/android/build.gradle +++ b/client/android/build.gradle @@ -138,7 +138,7 @@ android { resConfig "en" minSdkVersion = 24 targetSdkVersion = 34 - versionCode 36 // Change to a higher number + versionCode 37 // Change to a higher number versionName "4.0.8" // Change to a higher number javaCompileOptions.annotationProcessorOptions.arguments = [ From d98fdbdc5cd4a6b5cc2b15f33a910b300b8bc167 Mon Sep 17 00:00:00 2001 From: pokamest Date: Sat, 21 Oct 2023 14:17:45 +0100 Subject: [PATCH 270/278] Revert "added new drawer2type for replacing drawertype" --- client/resources.qrc | 1 - .../ConnectionTypeSelectionDrawer.qml | 7 +- .../qml/Components/HomeContainersListView.qml | 2 +- client/ui/qml/Components/QuestionDrawer.qml | 10 +- .../qml/Components/SelectLanguageDrawer.qml | 8 +- .../qml/Components/ShareConnectionDrawer.qml | 27 +- client/ui/qml/Controls2/Drawer2Type.qml | 314 ---------------- client/ui/qml/Controls2/DropDownType.qml | 21 +- client/ui/qml/Pages2/PageHome.qml | 340 +++++++++++------- .../qml/Pages2/PageProtocolCloakSettings.qml | 2 - .../Pages2/PageProtocolOpenVpnSettings.qml | 11 +- client/ui/qml/Pages2/PageProtocolRaw.qml | 15 +- .../PageProtocolShadowSocksSettings.qml | 2 - .../ui/qml/Pages2/PageServiceDnsSettings.qml | 7 +- .../ui/qml/Pages2/PageServiceSftpSettings.qml | 7 +- .../Pages2/PageServiceTorWebsiteSettings.qml | 7 +- .../ui/qml/Pages2/PageSettingsApplication.qml | 8 +- client/ui/qml/Pages2/PageSettingsBackup.qml | 7 +- client/ui/qml/Pages2/PageSettingsDns.qml | 7 +- client/ui/qml/Pages2/PageSettingsLogging.qml | 7 +- .../ui/qml/Pages2/PageSettingsServerData.qml | 24 +- .../ui/qml/Pages2/PageSettingsServerInfo.qml | 12 +- .../qml/Pages2/PageSettingsServerProtocol.qml | 7 +- .../qml/Pages2/PageSettingsSplitTunneling.qml | 29 +- .../PageSetupWizardProtocolSettings.qml | 11 +- client/ui/qml/Pages2/PageSetupWizardStart.qml | 3 +- client/ui/qml/Pages2/PageShare.qml | 7 - client/ui/qml/Pages2/PageStart.qml | 12 +- 28 files changed, 276 insertions(+), 639 deletions(-) delete mode 100644 client/ui/qml/Controls2/Drawer2Type.qml diff --git a/client/resources.qrc b/client/resources.qrc index 91578418..4c63383c 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -216,7 +216,6 @@ ui/qml/Pages2/PageServiceDnsSettings.qml ui/qml/Controls2/TopCloseButtonType.qml images/controls/x-circle.svg - ui/qml/Controls2/Drawer2Type.qml ui/qml/Pages2/PageProtocolAwgSettings.qml server_scripts/awg/template.conf server_scripts/awg/start.sh diff --git a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml index 71ec889f..1f7b2f29 100644 --- a/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml +++ b/client/ui/qml/Components/ConnectionTypeSelectionDrawer.qml @@ -8,16 +8,13 @@ import "../Controls2" import "../Controls2/TextTypes" import "../Config" -Drawer2Type { +DrawerType { id: root width: parent.width - height: parent.height - contentHeight: parent.height * 0.4375 + height: parent.height * 0.4375 ColumnLayout { - parent: root.contentParent - anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index d410f252..f05b90d6 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -63,7 +63,7 @@ ListView { isDefault = true menuContent.currentIndex = index - containersDropDown.menu.close() + containersDropDown.menuVisible = false } else { if (!isSupported && isInstalled) { PageController.showErrorMessage(qsTr("The selected protocol is not supported on the current platform")) diff --git a/client/ui/qml/Components/QuestionDrawer.qml b/client/ui/qml/Components/QuestionDrawer.qml index 72067f97..16cdcb39 100644 --- a/client/ui/qml/Components/QuestionDrawer.qml +++ b/client/ui/qml/Components/QuestionDrawer.qml @@ -5,7 +5,7 @@ import QtQuick.Layouts import "../Controls2" import "../Controls2/TextTypes" -Drawer2Type { +DrawerType { id: root property string headerText @@ -15,14 +15,12 @@ Drawer2Type { property var yesButtonFunction property var noButtonFunction - property real drawerHeight: 0.5 width: parent.width - height: parent.height - contentHeight: parent.height * drawerHeight + height: content.implicitHeight + 32 ColumnLayout { - parent: root.contentParent + id: content anchors.top: parent.top anchors.left: parent.left @@ -31,8 +29,6 @@ Drawer2Type { anchors.rightMargin: 16 anchors.leftMargin: 16 - // visible: false - spacing: 8 Header2TextType { diff --git a/client/ui/qml/Components/SelectLanguageDrawer.qml b/client/ui/qml/Components/SelectLanguageDrawer.qml index e6fdc2b5..d318aab8 100644 --- a/client/ui/qml/Components/SelectLanguageDrawer.qml +++ b/client/ui/qml/Components/SelectLanguageDrawer.qml @@ -5,18 +5,15 @@ import QtQuick.Layouts import "../Controls2" import "../Controls2/TextTypes" -Drawer2Type { +DrawerType { id: root width: parent.width - height: parent.height - contentHeight: parent.height * 0.9 + height: parent.height * 0.9 ColumnLayout { id: backButton - parent: root.contentParent - anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right @@ -31,7 +28,6 @@ Drawer2Type { } FlickableType { - parent: root.contentParent anchors.top: backButton.bottom anchors.left: parent.left anchors.right: parent.right diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index 03e7b500..1158dadc 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -16,7 +16,7 @@ import "../Controls2/TextTypes" import "../Config" import "../Components" -Drawer2Type { +DrawerType { id: root property alias headerText: header.headerText @@ -28,10 +28,9 @@ Drawer2Type { property string configFileName: "amnezia_config.vpn" width: parent.width - height: parent.height - contentHeight: parent.height * 0.9 + height: parent.height * 0.9 - onDrawerClosed: { + onClosed: { configExtension = ".vpn" configCaption = qsTr("Save AmneziaVPN config") configFileName = "amnezia_config" @@ -42,9 +41,6 @@ Drawer2Type { Header2Type { id: header - - parent: root.contentParent - anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right @@ -54,8 +50,6 @@ Drawer2Type { } FlickableType { - parent: root.contentParent - anchors.top: header.bottom anchors.bottom: parent.bottom contentHeight: content.height + 32 @@ -132,37 +126,30 @@ Drawer2Type { text: qsTr("Show connection settings") onClicked: { - configContentDrawer.open() + configContentDrawer.visible = true } } - Drawer2Type { + DrawerType { id: configContentDrawer - parent: root width: parent.width - height: parent.height - - contentHeight: parent.height * 0.9 + height: parent.height * 0.9 BackButtonType { id: backButton - parent: configContentDrawer.contentParent - anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right anchors.topMargin: 16 backButtonFunction: function() { - configContentDrawer.close() + configContentDrawer.visible = false } } FlickableType { - parent: configContentDrawer.contentParent - anchors.top: backButton.bottom anchors.left: parent.left anchors.right: parent.right diff --git a/client/ui/qml/Controls2/Drawer2Type.qml b/client/ui/qml/Controls2/Drawer2Type.qml deleted file mode 100644 index fa393982..00000000 --- a/client/ui/qml/Controls2/Drawer2Type.qml +++ /dev/null @@ -1,314 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls -import QtQuick.Shapes - -Item { - id: root - - Connections { - target: PageController - - function onForceCloseDrawer() { - if (root.expanded()) { - collapse() - } - } - } - - signal drawerClosed - signal collapsedEntered - signal collapsedExited - signal collapsedEnter - signal collapsedPressChanged - - - visible: false - - property bool needCloseButton: true - - property string defaultColor: "#1C1D21" - property string borderColor: "#2C2D30" - property string semitransparentColor: "#90000000" - - property bool needCollapsed: false - - property int contentHeight: 0 - property Item contentParent: contentArea - - property bool dragActive: dragArea.drag.active - - property int collapsedHeight: 0 - - property bool fullMouseAreaVisible: true - property MouseArea drawerDragArea: dragArea - - state: "collapsed" - - Rectangle { - id: draw2Background - - anchors.fill: parent - height: parent.height - width: parent.width - radius: 16 - color: "transparent" - border.color: "transparent" - border.width: 1 - visible: true - - MouseArea { - id: fullMouseArea - anchors.fill: parent - enabled: root.expanded() - hoverEnabled: true - visible: fullMouseAreaVisible - - onClicked: { - if (root.expanded()) { - collapse() - } - } - } - - Rectangle { - id: placeAreaHolder - - // for apdating home drawer, normal drawer will reset it - height: 0 - - anchors.right: parent.right - anchors.left: parent.left - visible: true - color: "transparent" - - Drag.active: dragArea.drag.active - } - - - Rectangle { - id: contentArea - - anchors.top: placeAreaHolder.bottom - height: contentHeight - radius: 16 - color: root.defaultColor - border.width: 1 - border.color: root.borderColor - width: parent.width - visible: true - - Rectangle { - width: parent.radius - height: parent.radius - anchors.bottom: parent.bottom - anchors.right: parent.right - anchors.left: parent.left - color: parent.color - } - - MouseArea { - id: dragArea - - anchors.fill: parent - - cursorShape: root.collapsed() ? Qt.PointingHandCursor : Qt.ArrowCursor - hoverEnabled: true - - drag.target: placeAreaHolder - drag.axis: Drag.YAxis - drag.maximumY: root.height - root.collapsedHeight - drag.minimumY: root.collapsedHeight > 0 ? root.height - root.height * 0.9 : 0 - - /** If drag area is released at any point other than min or max y, transition to the other state */ - onReleased: { - if (root.collapsed() && placeAreaHolder.y < drag.maximumY) { - root.state = "expanded" - return - } - if (root.expanded() && placeAreaHolder.y > drag.minimumY) { - root.state = "collapsed" - return - } - } - - onClicked: { - if (root.expanded()) { - collapse() - return - } - - if (root.collapsed()) { - root.state = "expanded" - } - } - - onExited: { - collapsedExited() - } - - onEntered: { - collapsedEnter() - } - - onPressedChanged: { - collapsedPressChanged() - } - } - } - } - - onStateChanged: { - if (root.collapsed()) { - var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor() - if (initialPageNavigationBarColor !== 0xFF1C1D21) { - PageController.updateNavigationBarColor(initialPageNavigationBarColor) - } - - if (needCloseButton) { - PageController.drawerClose() - } - - drawerClosed() - - return - } - - if (root.expanded()) { - if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) { - PageController.updateNavigationBarColor(0xFF1C1D21) - } - - if (needCloseButton) { - PageController.drawerOpen() - } - - return - } - } - - /** Two states of buttonContent, great place to add any future animations for the drawer */ - states: [ - State { - name: "collapsed" - PropertyChanges { - target: placeAreaHolder - y: dragArea.drag.maximumY - } - }, - - State { - name: "expanded" - PropertyChanges { - target: placeAreaHolder - y: dragArea.drag.minimumY - } - } - ] - - transitions: [ - Transition { - from: "expanded" - to: "collapsed" - PropertyAnimation { - target: placeAreaHolder - properties: "y" - duration: 200 - } - - onRunningChanged: { - if (!running) { - draw2Background.color = "transparent" - fullMouseArea.visible = false - } - } - }, - - Transition { - from: "collapsed" - to: "expanded" - PropertyAnimation { - target: placeAreaHolder - properties: "y" - duration: 200 - } - - onRunningChanged: { - if (!running) { - draw2Background.color = semitransparentColor - fullMouseArea.visible = true - } - } - } - ] - - NumberAnimation { - id: animationVisible - target: placeAreaHolder - property: "y" - from: parent.height - to: 0 - duration: 200 - } - - // for normal drawer - function open() { - if (root.expanded()) { - return - } - - draw2Background.color = semitransparentColor - fullMouseArea.visible = true - - collapsedHeight = 0 - - root.y = 0 - root.state = "expanded" - root.visible = true - root.height = parent.height - - contentArea.height = contentHeight - - placeAreaHolder.y = 0 - placeAreaHolder.height = root.height - contentHeight - - animationVisible.running = true - } - - function close() { - collapse() - } - - function collapse() { - draw2Background.color = "transparent" - root.state = "collapsed" - } - - // for page home - function expand() { - draw2Background.color = semitransparentColor - root.state = "expanded" - } - - function expanded() { - return root.state === "expanded" ? true : false - } - - function collapsed() { - return root.state === "collapsed" ? true : false - } - - - onVisibleChanged: { - // e.g cancel, ...... - if (!visible) { - if (root.expanded()) { - if (needCloseButton) { - PageController.drawerClose() - } - - close() - } - } - } -} diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index c666408a..b91f0b7a 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -40,10 +40,6 @@ Item { property alias menuVisible: menu.visible - property Item drawerParent: root - - property Drawer2Type menu: menu - implicitWidth: rootButtonContent.implicitWidth implicitHeight: rootButtonContent.implicitHeight @@ -159,26 +155,21 @@ Item { onClicked: { if (rootButtonClickedFunction && typeof rootButtonClickedFunction === "function") { rootButtonClickedFunction() + } else { + menu.visible = true } - - menu.open() } } - Drawer2Type { + DrawerType { id: menu - parent: drawerParent - width: parent.width - height: parent.height - contentHeight: parent.height * drawerHeight + height: parent.height * drawerHeight ColumnLayout { id: header - parent: menu.contentParent - anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right @@ -187,14 +178,12 @@ Item { BackButtonType { backButtonImage: root.headerBackButtonImage backButtonFunction: function() { - menu.close() + root.menuVisible = false } } } FlickableType { - parent: menu.contentParent - anchors.top: header.bottom anchors.topMargin: 16 contentHeight: col.implicitHeight diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 414d604b..cc49e4f0 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -30,13 +30,13 @@ PageType { target: PageController function onRestorePageHomeState(isContainerInstalled) { - buttonContent.collapse() + buttonContent.state = "expanded" if (isContainerInstalled) { containersDropDown.menuVisible = true } } function onForceCloseDrawer() { - buttonContent.collapse() + buttonContent.state = "collapsed" } } @@ -73,8 +73,14 @@ PageType { expandedServersMenuDescription.text = description + root.defaultServerHostName } - Component.onCompleted: { - updateDescriptions() + Component.onCompleted: updateDescriptions() + + MouseArea { + anchors.fill: parent + enabled: buttonContent.state === "expanded" + onClicked: { + buttonContent.state = "collapsed" + } } Item { @@ -86,10 +92,56 @@ PageType { } } + MouseArea { + id: dragArea + + anchors.fill: buttonBackground + cursorShape: buttonContent.state === "collapsed" ? Qt.PointingHandCursor : Qt.ArrowCursor + hoverEnabled: true + + drag.target: buttonContent + drag.axis: Drag.YAxis + drag.maximumY: root.height - buttonContent.collapsedHeight + drag.minimumY: root.height - root.height * 0.9 + + /** If drag area is released at any point other than min or max y, transition to the other state */ + onReleased: { + if (buttonContent.state === "collapsed" && buttonContent.y < dragArea.drag.maximumY) { + buttonContent.state = "expanded" + return + } + if (buttonContent.state === "expanded" && buttonContent.y > dragArea.drag.minimumY) { + buttonContent.state = "collapsed" + return + } + } + + onEntered: { + collapsedButtonChevron.backgroundColor = collapsedButtonChevron.hoveredColor + collapsedButtonHeader.opacity = 0.8 + } + onExited: { + collapsedButtonChevron.backgroundColor = collapsedButtonChevron.defaultColor + collapsedButtonHeader.opacity = 1 + } + onPressedChanged: { + collapsedButtonChevron.backgroundColor = pressed ? collapsedButtonChevron.pressedColor : entered ? collapsedButtonChevron.hoveredColor : collapsedButtonChevron.defaultColor + collapsedButtonHeader.opacity = 0.7 + } + + + onClicked: { + if (buttonContent.state === "collapsed") { + buttonContent.state = "expanded" + } + } + } + Rectangle { id: buttonBackground - anchors { left: buttonContent.left; right: buttonContent.right; top: buttonContent.top } + anchors { left: buttonContent.left; right: buttonContent.right; top: buttonContent.top } + height: root.height radius: 16 color: root.defaultColor border.color: root.borderColor @@ -105,126 +157,161 @@ PageType { } } - Drawer2Type { + ColumnLayout { id: buttonContent - visible: true - - fullMouseAreaVisible: false + /** Initial height of button content */ + property int collapsedHeight: 0 /** True when expanded objects should be visible */ - property bool expandedVisibility: buttonContent.expanded() || (buttonContent.collapsed() && buttonContent.dragActive) + property bool expandedVisibility: buttonContent.state === "expanded" || (buttonContent.state === "collapsed" && dragArea.drag.active === true) /** True when collapsed objects should be visible */ - property bool collapsedVisibility: buttonContent.collapsed() && !buttonContent.dragActive + property bool collapsedVisibility: buttonContent.state === "collapsed" && dragArea.drag.active === false - width: parent.width - height: parent.height - contentHeight: parent.height * 0.9 + Drag.active: dragArea.drag.active + anchors.right: root.right + anchors.left: root.left + y: root.height - buttonContent.height + Component.onCompleted: { + buttonContent.state = "collapsed" + } - ColumnLayout { - id: collapsedButtonContent - - parent: buttonContent.contentParent - - visible: buttonContent.collapsedVisibility - - anchors.right: parent.right - anchors.left: parent.left - anchors.top: parent.top - - onImplicitHeightChanged: { - if (buttonContent.collapsed() && buttonContent.collapsedHeight === 0) { - buttonContent.collapsedHeight = implicitHeight - } - } - - DividerType { - Layout.topMargin: 10 - Layout.fillWidth: false - Layout.preferredWidth: 20 - Layout.preferredHeight: 2 - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - } - - RowLayout { - Layout.topMargin: 14 - Layout.leftMargin: 24 - Layout.rightMargin: 24 - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - - Header1TextType { - id: collapsedButtonHeader - Layout.maximumWidth: root.width - 48 - 18 - 12 // todo - - maximumLineCount: 2 - elide: Qt.ElideRight - - text: root.defaultServerName - - Layout.alignment: Qt.AlignLeft - } - - - ImageButtonType { - id: collapsedButtonChevron - - hoverEnabled: false - image: "qrc:/images/controls/chevron-down.svg" - imageColor: "#d7d8db" - - horizontalPadding: 0 - padding: 0 - spacing: 0 - - Rectangle { - id: rightImageBackground - anchors.fill: parent - radius: 16 - color: "transparent" - - Behavior on color { - PropertyAnimation { duration: 200 } - } - } - - onClicked: { - if (buttonContent.collapsed()) { - buttonContent.expand() - } - } - } - } - - LabelTextType { - id: collapsedServerMenuDescription - Layout.bottomMargin: 44 - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - visible: buttonContent.collapsedVisibility + /** Set once based on first implicit height change once all children are layed out */ + onImplicitHeightChanged: { + if (buttonContent.state === "collapsed" && collapsedHeight == 0) { + collapsedHeight = implicitHeight } } - Component.onCompleted: { - buttonContent.collapse() + onStateChanged: { + if (buttonContent.state === "collapsed") { + var initialPageNavigationBarColor = PageController.getInitialPageNavigationBarColor() + if (initialPageNavigationBarColor !== 0xFF1C1D21) { + PageController.updateNavigationBarColor(initialPageNavigationBarColor) + } + PageController.drawerClose() + return + } + if (buttonContent.state === "expanded") { + if (PageController.getInitialPageNavigationBarColor() !== 0xFF1C1D21) { + PageController.updateNavigationBarColor(0xFF1C1D21) + } + PageController.drawerOpen() + return + } + } + + /** Two states of buttonContent, great place to add any future animations for the drawer */ + states: [ + State { + name: "collapsed" + PropertyChanges { + target: buttonContent + y: root.height - collapsedHeight + } + }, + State { + name: "expanded" + PropertyChanges { + target: buttonContent + y: dragArea.drag.minimumY + + } + } + ] + + transitions: [ + Transition { + from: "collapsed" + to: "expanded" + PropertyAnimation { + target: buttonContent + properties: "y" + duration: 200 + } + }, + Transition { + from: "expanded" + to: "collapsed" + PropertyAnimation { + target: buttonContent + properties: "y" + duration: 200 + } + } + ] + + DividerType { + Layout.topMargin: 10 + Layout.fillWidth: false + Layout.preferredWidth: 20 + Layout.preferredHeight: 2 + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + + visible: (buttonContent.collapsedVisibility || buttonContent.expandedVisibility) + } + + RowLayout { + Layout.topMargin: 14 + Layout.leftMargin: 24 + Layout.rightMargin: 24 + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + visible: buttonContent.collapsedVisibility + + spacing: 0 + + Header1TextType { + id: collapsedButtonHeader + Layout.maximumWidth: buttonContent.width - 48 - 18 - 12 // todo + + maximumLineCount: 2 + elide: Qt.ElideRight + + text: root.defaultServerName + horizontalAlignment: Qt.AlignHCenter + + Behavior on opacity { + PropertyAnimation { duration: 200 } + } + } + + ImageButtonType { + id: collapsedButtonChevron + + Layout.leftMargin: 8 + + hoverEnabled: false + image: "qrc:/images/controls/chevron-down.svg" + imageColor: "#d7d8db" + + icon.width: 18 + icon.height: 18 + backgroundRadius: 16 + horizontalPadding: 4 + topPadding: 4 + bottomPadding: 3 + + onClicked: { + if (buttonContent.state === "collapsed") { + buttonContent.state = "expanded" + } + } + } + } + + LabelTextType { + id: collapsedServerMenuDescription + Layout.bottomMargin: 44 + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter + visible: buttonContent.collapsedVisibility } ColumnLayout { id: serversMenuHeader - parent: buttonContent.contentParent - - anchors.top: parent.top - anchors.right: parent.right - anchors.left: parent.left - + Layout.alignment: Qt.AlignTop | Qt.AlignHCenter + Layout.fillWidth: true visible: buttonContent.expandedVisibility - - DividerType { - Layout.topMargin: 10 - Layout.fillWidth: false - Layout.preferredWidth: 20 - Layout.preferredHeight: 2 - Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter - } Header1TextType { Layout.fillWidth: true @@ -253,8 +340,6 @@ PageType { DropDownType { id: containersDropDown - drawerParent: root - rootButtonImageColor: "#0E0E11" rootButtonBackgroundColor: "#D7D8DB" rootButtonBackgroundHoveredColor: Qt.rgba(215, 216, 219, 0.8) @@ -316,18 +401,12 @@ PageType { Flickable { id: serversContainer - - parent: buttonContent.contentParent - - anchors.top: serversMenuHeader.bottom - anchors.right: parent.right - anchors.left: parent.left - anchors.bottom: parent.bottom - anchors.topMargin: 16 + Layout.alignment: Qt.AlignTop | Qt.AlignHCenter + Layout.fillWidth: true + Layout.topMargin: 16 contentHeight: col.implicitHeight - + implicitHeight: root.height - (root.height * 0.1) - serversMenuHeader.implicitHeight - 52 //todo 52 is tabbar height visible: buttonContent.expandedVisibility - clip: true ScrollBar.vertical: ScrollBar { @@ -437,7 +516,7 @@ PageType { onClicked: function() { ServersModel.currentlyProcessedIndex = index PageController.goToPage(PageEnum.PageSettingsServerInfo) - buttonContent.collapse() + buttonContent.state = "collapsed" } } } @@ -452,22 +531,5 @@ PageType { } } } - - onCollapsedEnter: { - collapsedButtonChevron.backgroundColor = collapsedButtonChevron.hoveredColor - collapsedButtonHeader.opacity = 0.8 - } - - onCollapsedExited: { - collapsedButtonChevron.backgroundColor = collapsedButtonChevron.defaultColor - collapsedButtonHeader.opacity = 1 - } - - onCollapsedPressChanged: { - collapsedButtonChevron.backgroundColor = buttonContent.drawerDragArea.pressed ? - collapsedButtonChevron.pressedColor : buttonContent.drawerDragArea.entered ? - collapsedButtonChevron.hoveredColor : collapsedButtonChevron.defaultColor - collapsedButtonHeader.opacity = 0.7 - } } } diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml index b0f36971..78e666a7 100644 --- a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -117,8 +117,6 @@ PageType { Layout.fillWidth: true Layout.topMargin: 16 - drawerParent: root - descriptionText: qsTr("Cipher") headerText: qsTr("Cipher") diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index 8ce5e554..cd2dd51d 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -157,8 +157,6 @@ PageType { Layout.fillWidth: true Layout.topMargin: 20 - drawerParent: root - enabled: !autoNegotiateEncryprionSwitcher.checked descriptionText: qsTr("Hash") @@ -205,8 +203,6 @@ PageType { Layout.fillWidth: true Layout.topMargin: 16 - drawerParent: root - enabled: !autoNegotiateEncryprionSwitcher.checked descriptionText: qsTr("Cipher") @@ -374,14 +370,14 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.close() + questionDrawer.visible = false PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeCurrentlyProcessedContainer() } questionDrawer.noButtonFunction = function() { - questionDrawer.close() + questionDrawer.visible = false } - questionDrawer.open() + questionDrawer.visible = true } } @@ -406,7 +402,6 @@ PageType { QuestionDrawer { id: questionDrawer - parent: root } } } diff --git a/client/ui/qml/Pages2/PageProtocolRaw.qml b/client/ui/qml/Pages2/PageProtocolRaw.qml index accfd0bd..f3621d96 100644 --- a/client/ui/qml/Pages2/PageProtocolRaw.qml +++ b/client/ui/qml/Pages2/PageProtocolRaw.qml @@ -90,19 +90,15 @@ PageType { DividerType {} - Drawer2Type { + DrawerType { id: configContentDrawer - parent: root width: parent.width - height: parent.height - contentHeight: parent.height * 0.9 + height: parent.height * 0.9 BackButtonType { id: backButton - parent: configContentDrawer.contentParent - anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right @@ -185,14 +181,14 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.close() + questionDrawer.visible = false PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeCurrentlyProcessedContainer() } questionDrawer.noButtonFunction = function() { - questionDrawer.close() + questionDrawer.visible = false } - questionDrawer.open() + questionDrawer.visible = true } MouseArea { @@ -207,7 +203,6 @@ PageType { QuestionDrawer { id: questionDrawer - parent: root } } } diff --git a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml index 75853d1f..2453281f 100644 --- a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml @@ -95,8 +95,6 @@ PageType { Layout.fillWidth: true Layout.topMargin: 20 - drawerParent: root - descriptionText: qsTr("Cipher") headerText: qsTr("Cipher") diff --git a/client/ui/qml/Pages2/PageServiceDnsSettings.qml b/client/ui/qml/Pages2/PageServiceDnsSettings.qml index 9ad3b289..10fe6f56 100644 --- a/client/ui/qml/Pages2/PageServiceDnsSettings.qml +++ b/client/ui/qml/Pages2/PageServiceDnsSettings.qml @@ -68,14 +68,14 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.close() + questionDrawer.visible = false PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeCurrentlyProcessedContainer() } questionDrawer.noButtonFunction = function() { - questionDrawer.close() + questionDrawer.visible = false } - questionDrawer.open() + questionDrawer.visible = true } MouseArea { @@ -89,7 +89,6 @@ PageType { QuestionDrawer { id: questionDrawer - parent: root } } } diff --git a/client/ui/qml/Pages2/PageServiceSftpSettings.qml b/client/ui/qml/Pages2/PageServiceSftpSettings.qml index 1a591268..b12302dd 100644 --- a/client/ui/qml/Pages2/PageServiceSftpSettings.qml +++ b/client/ui/qml/Pages2/PageServiceSftpSettings.qml @@ -253,14 +253,14 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.close() + questionDrawer.visible = false PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeCurrentlyProcessedContainer() } questionDrawer.noButtonFunction = function() { - questionDrawer.close() + questionDrawer.visible = false } - questionDrawer.open() + questionDrawer.visible = true } } } @@ -270,7 +270,6 @@ PageType { QuestionDrawer { id: questionDrawer - parent: root } } } diff --git a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml index 9771c4db..3bfa5bb0 100644 --- a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml +++ b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml @@ -131,21 +131,20 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.close() + questionDrawer.visible = false PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeCurrentlyProcessedContainer() } questionDrawer.noButtonFunction = function() { - questionDrawer.close() + questionDrawer.visible = false } - questionDrawer.open() + questionDrawer.visible = true } } } QuestionDrawer { id: questionDrawer - parent: root } } } diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index 31f59438..05e468f0 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -119,7 +119,6 @@ PageType { SelectLanguageDrawer { id: selectLanguageDrawer - parent: root } @@ -152,14 +151,14 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.close() + questionDrawer.visible = false SettingsController.clearSettings() PageController.replaceStartPage() } questionDrawer.noButtonFunction = function() { - questionDrawer.close() + questionDrawer.visible = false } - questionDrawer.open() + questionDrawer.visible = true } } @@ -167,7 +166,6 @@ PageType { QuestionDrawer { id: questionDrawer - parent: root } } } diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml index eabf1f48..81be0465 100644 --- a/client/ui/qml/Pages2/PageSettingsBackup.qml +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -139,19 +139,18 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.close() + questionDrawer.visible = false PageController.showBusyIndicator(true) SettingsController.restoreAppConfig(filePath) PageController.showBusyIndicator(false) } questionDrawer.noButtonFunction = function() { - questionDrawer.close() + questionDrawer.visible = false } - questionDrawer.open() + questionDrawer.visible = true } QuestionDrawer { id: questionDrawer - parent: root } } diff --git a/client/ui/qml/Pages2/PageSettingsDns.qml b/client/ui/qml/Pages2/PageSettingsDns.qml index 8afb9bcc..5670464f 100644 --- a/client/ui/qml/Pages2/PageSettingsDns.qml +++ b/client/ui/qml/Pages2/PageSettingsDns.qml @@ -92,7 +92,7 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.close() + questionDrawer.visible = false SettingsController.primaryDns = "1.1.1.1" primaryDns.textFieldText = SettingsController.primaryDns SettingsController.secondaryDns = "1.0.0.1" @@ -100,9 +100,9 @@ PageType { PageController.showNotificationMessage(qsTr("Settings have been reset")) } questionDrawer.noButtonFunction = function() { - questionDrawer.close() + questionDrawer.visible = false } - questionDrawer.open() + questionDrawer.visible = true } } @@ -124,7 +124,6 @@ PageType { } QuestionDrawer { id: questionDrawer - parent: root } } } diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml index d282d0f0..840c41d4 100644 --- a/client/ui/qml/Pages2/PageSettingsLogging.qml +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -147,16 +147,16 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.close() + questionDrawer.visible = false PageController.showBusyIndicator(true) SettingsController.clearLogs() PageController.showBusyIndicator(false) PageController.showNotificationMessage(qsTr("Logs have been cleaned up")) } questionDrawer.noButtonFunction = function() { - questionDrawer.close() + questionDrawer.visible = false } - questionDrawer.open() + questionDrawer.visible = true } } @@ -172,7 +172,6 @@ PageType { QuestionDrawer { id: questionDrawer - parent: root } } } diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 09066ccb..3eb07ce9 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -14,8 +14,6 @@ import "../Components" PageType { id: root - property Item questionDrawerParent - Connections { target: InstallController @@ -96,15 +94,15 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.close() + questionDrawer.visible = false PageController.showBusyIndicator(true) SettingsController.clearCachedProfiles() PageController.showBusyIndicator(false) } questionDrawer.noButtonFunction = function() { - questionDrawer.close() + questionDrawer.visible = false } - questionDrawer.open() + questionDrawer.visible = true } } @@ -143,7 +141,7 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.close() + questionDrawer.visible = false PageController.showBusyIndicator(true) if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { ConnectionController.closeConnection() @@ -152,9 +150,9 @@ PageType { PageController.showBusyIndicator(false) } questionDrawer.noButtonFunction = function() { - questionDrawer.close() + questionDrawer.visible = false } - questionDrawer.open() + questionDrawer.visible = true } } @@ -174,7 +172,7 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.close() + questionDrawer.visible = false PageController.goToPage(PageEnum.PageDeinstalling) if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { ConnectionController.closeConnection() @@ -182,9 +180,9 @@ PageType { InstallController.removeAllContainers() } questionDrawer.noButtonFunction = function() { - questionDrawer.close() + questionDrawer.visible = false } - questionDrawer.open() + questionDrawer.visible = true } } @@ -194,10 +192,6 @@ PageType { QuestionDrawer { id: questionDrawer - - drawerHeight: 0.5 - - parent: questionDrawerParent } } } diff --git a/client/ui/qml/Pages2/PageSettingsServerInfo.qml b/client/ui/qml/Pages2/PageSettingsServerInfo.qml index e14c6ab5..e2e7868c 100644 --- a/client/ui/qml/Pages2/PageSettingsServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsServerInfo.qml @@ -71,17 +71,15 @@ PageType { } actionButtonFunction: function() { - serverNameEditDrawer.open() + serverNameEditDrawer.visible = true } } - Drawer2Type { + DrawerType { id: serverNameEditDrawer - parent: root width: root.width - height: root.height // * 0.35 - contentHeight: root.height * 0.35 + height: root.height * 0.35 onVisibleChanged: { if (serverNameEditDrawer.visible) { @@ -90,8 +88,6 @@ PageType { } ColumnLayout { - parent: serverNameEditDrawer.contentParent - anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right @@ -99,7 +95,6 @@ PageType { anchors.leftMargin: 16 anchors.rightMargin: 16 - TextFieldWithHeaderType { id: serverName @@ -169,7 +164,6 @@ PageType { } PageSettingsServerData { stackView: root.stackView - questionDrawerParent: root } } } diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml index a518167c..30758da4 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -119,14 +119,14 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.close() + questionDrawer.visible = false PageController.goToPage(PageEnum.PageDeinstalling) InstallController.removeCurrentlyProcessedContainer() } questionDrawer.noButtonFunction = function() { - questionDrawer.close() + questionDrawer.visible = false } - questionDrawer.open() + questionDrawer.visible = true } MouseArea { @@ -141,7 +141,6 @@ PageType { QuestionDrawer { id: questionDrawer - parent: root } } } diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index 7811e2bb..cc4973f1 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -116,8 +116,6 @@ PageType { DropDownType { id: selector - drawerParent: root - Layout.fillWidth: true Layout.topMargin: 32 Layout.leftMargin: 16 @@ -210,13 +208,13 @@ PageType { questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.yesButtonFunction = function() { - questionDrawer.close() + questionDrawer.visible = false SitesController.removeSite(index) } questionDrawer.noButtonFunction = function() { - questionDrawer.close() + questionDrawer.visible = false } - questionDrawer.open() + questionDrawer.visible = true } } @@ -224,7 +222,6 @@ PageType { QuestionDrawer { id: questionDrawer - parent: root } } } @@ -279,18 +276,13 @@ PageType { } } - Drawer2Type { + DrawerType { id: moreActionsDrawer width: parent.width - height: parent.height - contentHeight: parent.height * 0.4375 - - parent: root + height: parent.height * 0.4375 FlickableType { - parent: moreActionsDrawer.contentParent - anchors.fill: parent contentHeight: moreActionsDrawerContent.height ColumnLayout { @@ -349,20 +341,15 @@ PageType { } } - Drawer2Type { + DrawerType { id: importSitesDrawer width: parent.width - height: parent.height - contentHeight: parent.height * 0.4375 - - parent: root + height: parent.height * 0.4375 BackButtonType { id: importSitesDrawerBackButton - parent: importSitesDrawer.contentParent - anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right @@ -374,8 +361,6 @@ PageType { } FlickableType { - parent: importSitesDrawer.contentParent - anchors.top: importSitesDrawerBackButton.bottom anchors.left: parent.left anchors.right: parent.right diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 86d3c1fd..7698c755 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -97,20 +97,15 @@ PageType { } } - Drawer2Type { + DrawerType { id: showDetailsDrawer width: parent.width - height: parent.height - contentHeight: parent.height * 0.9 - - parent: root + height: parent.height * 0.9 BackButtonType { id: showDetailsBackButton - parent: showDetailsDrawer.contentParent - anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right @@ -122,8 +117,6 @@ PageType { } FlickableType { - parent: showDetailsDrawer.contentParent - anchors.top: showDetailsBackButton.bottom anchors.left: parent.left anchors.right: parent.right diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index 059aedab..ba78c985 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -115,7 +115,7 @@ PageType { text: qsTr("I have the data to connect") onClicked: { - connectionTypeSelection.open() + connectionTypeSelection.visible = true } } @@ -140,7 +140,6 @@ PageType { ConnectionTypeSelectionDrawer { id: connectionTypeSelection - parent: root } } diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 5e97cb42..25aad3de 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -179,8 +179,6 @@ PageType { DropDownType { id: serverSelector - drawerParent: root - signal severSelectorIndexChanged property int currentIndex: 0 @@ -243,8 +241,6 @@ PageType { DropDownType { id: protocolSelector - drawerParent: root - visible: accessTypeSelector.currentIndex === 0 Layout.fillWidth: true @@ -334,8 +330,6 @@ PageType { DropDownType { id: exportTypeSelector - drawerParent: root - property int currentIndex: 0 Layout.fillWidth: true @@ -377,7 +371,6 @@ PageType { ShareConnectionDrawer { id: shareConnectionDrawer - parent: root } BasicButtonType { diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 65c351bf..ab02ace4 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -135,8 +135,6 @@ PageType { var pagePath = PageController.getPagePath(PageEnum.PageHome) ServersModel.currentlyProcessedIndex = ServersModel.defaultIndex tabBarStackView.push(pagePath, { "objectName" : pagePath }) - - connectionTypeSelection.parent = tabBarStackView } // onWidthChanged: { @@ -176,12 +174,6 @@ PageType { strokeColor: "#2C2D30" fillColor: "#1C1D21" } - - MouseArea { - id: noPropagateMouseEvent - anchors.fill: parent - enabled: true - } } TabImageButtonType { @@ -252,9 +244,7 @@ PageType { ConnectionTypeSelectionDrawer { id: connectionTypeSelection - z: 1 - - onDrawerClosed: { + onAboutToHide: { tabBar.setCurrentIndex(tabBar.previousIndex) } } From 99214e22e30c630aafa3a3e59c4b5444ac7c7abb Mon Sep 17 00:00:00 2001 From: pokamest Date: Sat, 21 Oct 2023 16:05:09 +0100 Subject: [PATCH 271/278] Fix docs url --- client/ui/qml/Pages2/PageSetupWizardStart.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ui/qml/Pages2/PageSetupWizardStart.qml b/client/ui/qml/Pages2/PageSetupWizardStart.qml index ba78c985..994ec200 100644 --- a/client/ui/qml/Pages2/PageSetupWizardStart.qml +++ b/client/ui/qml/Pages2/PageSetupWizardStart.qml @@ -134,7 +134,7 @@ PageType { text: qsTr("I have nothing") - onClicked: Qt.openUrlExternally("https://ru-docs.amnezia.org/guides/hosting-instructions") + onClicked: Qt.openUrlExternally("https://amnezia.org/instructions/0_starter-guide") } } From e16a1100d8bc2f762d14e24de9800e20eeeac53b Mon Sep 17 00:00:00 2001 From: pokamest Date: Sat, 21 Oct 2023 16:20:57 +0100 Subject: [PATCH 272/278] Update amneziavpn_ru.ts --- client/translations/amneziavpn_ru.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index 281e4edb..eb19ad82 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -2571,9 +2571,9 @@ OpenVPN обеспечивает безопасное VPN-соединение Cloak защищает OpenVPN от обнаружения и блокировок. -Cloak может изменять метаданные пакетов. Он полностью маскирует VPN-трафик под обычный веб-трафик, а также защищает VPN от обнаружения с помощью Active Probing. Это делает ее очень устойчивой к обнаружению +Cloak может изменять метаданные пакетов. Он полностью маскирует VPN-трафик под обычный веб-трафик, а также защищает VPN от обнаружения с помощью Active Probing. Это делает его очень устойчивым к обнаружению -Сразу же после получения первого пакета данных Cloak проверяет подлинность входящего соединения. Если аутентификация не проходит, плагин маскирует сервер под поддельный сайт, и ваша VPN становится невидимой для аналитических систем. +Сразу же после получения первого пакета данных Cloak проверяет подлинность входящего соединения. Если аутентификация не проходит, плагин маскирует сервер под поддельный сайт, и ваш VPN становится невидимым для аналитических систем. Если в вашем регионе существует экстремальный уровень цензуры в Интернете, мы советуем вам при первом подключении использовать только OpenVPN через Cloak From 7a54dc15da5070e744d9d9be0cc9bc72d4790e52 Mon Sep 17 00:00:00 2001 From: pokamest Date: Sat, 21 Oct 2023 16:33:21 +0100 Subject: [PATCH 273/278] Update amneziavpn_ru.ts --- client/translations/amneziavpn_ru.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index eb19ad82..83cab7ae 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -404,7 +404,7 @@ Already installed containers were found on the server. All installed containers VPN Addresses Subnet - VPN Адреса Подсеть + Подсеть для VPN @@ -1132,7 +1132,7 @@ Already installed containers were found on the server. All installed containers Connection - Подключение + Соединение @@ -1278,7 +1278,7 @@ Already installed containers were found on the server. All installed containers Save logs to file - Сохранять логи в файл + Сохранить логи в файл From 0bb4dd94423bc8bc2a58e980fb0af31340e76c72 Mon Sep 17 00:00:00 2001 From: pokamest Date: Sat, 21 Oct 2023 18:32:30 +0100 Subject: [PATCH 274/278] Text and translations fixes --- client/translations/amneziavpn_ru.ts | 62 ++++++++++++------- client/translations/amneziavpn_zh_CN.ts | 58 +++++++++++------ .../ui/qml/Pages2/PageProtocolAwgSettings.qml | 2 +- .../Pages2/PageProtocolOpenVpnSettings.qml | 2 +- client/ui/qml/Pages2/PageProtocolRaw.qml | 2 +- .../qml/Pages2/PageSettingsServerProtocol.qml | 2 +- client/ui/qml/Pages2/PageShare.qml | 6 +- 7 files changed, 85 insertions(+), 49 deletions(-) diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index 83cab7ae..f4ef9c0f 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -120,7 +120,7 @@ Unable change protocol while there is an active connection - + Невозможно изменить протокол при активном соединении @@ -274,7 +274,7 @@ Already installed containers were found on the server. All installed containers Unable change server while there is an active connection - + Невозможно изменить сервер при активном соединении @@ -346,9 +346,13 @@ Already installed containers were found on the server. All installed containers - All users who you shared a connection with will no longer be able to connect to it. + All users with whom you shared a connection will no longer be able to connect to it. Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. + + All users who you shared a connection with will no longer be able to connect to it. + Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. + Continue @@ -571,9 +575,13 @@ Already installed containers were found on the server. All installed containers - All users who you shared a connection with will no longer be able to connect to it. + All users with whom you shared a connection will no longer be able to connect to it. Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. + + All users who you shared a connection with will no longer be able to connect to it. + Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. + Continue @@ -619,8 +627,12 @@ Already installed containers were found on the server. All installed containers + All users with whom you shared a connection will no longer be able to connect to it. + Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. + + All users who you shared a connection with will no longer be able to connect to it. - Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. + Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. @@ -1440,8 +1452,12 @@ Already installed containers were found on the server. All installed containers + All users with whom you shared a connection will no longer be able to connect to it. + Все пользователи, с которыми вы поделились этим VPN-протоколом, больше не смогут к нему подключаться. + + All users who you shared a connection with will no longer be able to connect to it. - Все пользователи, которым вы поделились VPN, больше не смогут к нему подключаться. + Все пользователи, которым вы поделились VPN, больше не смогут к нему подключаться. @@ -1884,9 +1900,8 @@ and will not be shared or disclosed to the Amnezia or any third parties WireGuard нативный формат - VPN Access - VPN-Доступ + VPN-Доступ @@ -1894,14 +1909,12 @@ and will not be shared or disclosed to the Amnezia or any third parties Соединение - VPN access without the ability to manage the server - Доступ к VPN, без возможности управления сервером + Доступ к VPN, без возможности управления сервером - Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the server, as well as change settings. - Доступ к управлению сервером. Пользователь, с которым вы делитесь полным доступом к соединению, сможет добавлять и удалять ваши протоколы и службы на сервере, а также изменять настройки. + Доступ к управлению сервером. Пользователь, с которым вы делитесь полным доступом к соединению, сможет добавлять и удалять ваши протоколы и службы на сервере, а также изменять настройки. @@ -1944,11 +1957,26 @@ and will not be shared or disclosed to the Amnezia or any third parties For the AmneziaVPN app Для AmneziaVPN + + + Share VPN Access + Поделиться VPN + Full access Полный доступ + + + Share VPN access without the ability to manage the server + Поделиться доступом к VPN, без возможности управления сервером + + + + Share access to server management. The user with whom you share full access to the server will be able to add and remove any protocols and services to the server, as well as change settings. + Поделиться доступом к управлению сервером. Пользователь, с которым вы делитесь полным доступом к серверу, сможет добавлять и удалять любые протоколы и службы на сервере, а также изменять настройки. + @@ -2700,16 +2728,6 @@ This means that AmneziaWG keeps the fast performance of the original while addin error 0x%1: %2 error 0x%1: %2 - - - WireGuard Configuration Highlighter - - - - - &Randomize colors - - SelectLanguageDrawer diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index 8e31af68..c711b3fc 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -131,7 +131,7 @@ Unable change protocol while there is an active connection - 已建立连接时无法更改服务器配置 + 已建立连接时无法更改服务器配置 @@ -380,8 +380,12 @@ Already installed containers were found on the server. All installed containers + All users with whom you shared a connection will no longer be able to connect to it. + 与您共享连接的所有用户将无法再连接到该连接。 + + All users who you shared a connection with will no longer be able to connect to it. - 使用此共享连接的所有用户,将无法再连接它。 + 使用此共享连接的所有用户,将无法再连接它。 @@ -605,8 +609,12 @@ Already installed containers were found on the server. All installed containers + All users with whom you shared a connection will no longer be able to connect to it. + 与您共享连接的所有用户将无法再连接到该连接。 + + All users who you shared a connection with will no longer be able to connect to it. - 使用此共享连接的所有用户,将无法再连接它。 + 使用此共享连接的所有用户,将无法再连接它。 All users with whom you shared a connection will no longer be able to connect to it @@ -661,8 +669,12 @@ Already installed containers were found on the server. All installed containers + All users with whom you shared a connection will no longer be able to connect to it. + 与您共享连接的所有用户将无法再连接到该连接。 + + All users who you shared a connection with will no longer be able to connect to it. - 使用此共享连接的所有用户,将无法再连接它。 + 使用此共享连接的所有用户,将无法再连接它。 from server? @@ -1521,8 +1533,12 @@ And if you don't like the app, all the more support it - the donation will + All users with whom you shared a connection will no longer be able to connect to it. + 与您共享连接的所有用户将无法再连接到该连接。 + + All users who you shared a connection with will no longer be able to connect to it. - 使用此共享连接的所有用户,将无法再连接它。 + 使用此共享连接的所有用户,将无法再连接它。 from server? @@ -2006,8 +2022,22 @@ and will not be shared or disclosed to the Amnezia or any third parties + Share VPN Access + 共享 VPN 访问 + + + + Share VPN access without the ability to manage the server + 共享 VPN 访问,无需管理服务器 + + + + Share access to server management. The user with whom you share full access to the server will be able to add and remove any protocols and services to the server, as well as change settings. + 共享服务器管理访问权限。与您共享服务器全部访问权限的用户将可以添加和删除服务器上的任何协议和服务,以及更改设置。 + + VPN Access - 访问VPN + 访问VPN @@ -2020,14 +2050,12 @@ and will not be shared or disclosed to the Amnezia or any third parties 完全访问 - VPN access without the ability to manage the server - 访问VPN,但没有权限管理服务。 + 访问VPN,但没有权限管理服务。 - Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the server, as well as change settings. - 除访问VPN外,用户还能添加和删除协议、服务以及更改配置信息 + 除访问VPN外,用户还能添加和删除协议、服务以及更改配置信息 Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the servers, as well as change settings. @@ -2839,16 +2867,6 @@ While it offers a blend of security, stability, and speed, it's essential t error 0x%1: %2 错误 0x%1: %2 - - - WireGuard Configuration Highlighter - - - - - &Randomize colors - - SelectLanguageDrawer diff --git a/client/ui/qml/Pages2/PageProtocolAwgSettings.qml b/client/ui/qml/Pages2/PageProtocolAwgSettings.qml index 079c85a1..237a8b46 100644 --- a/client/ui/qml/Pages2/PageProtocolAwgSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolAwgSettings.qml @@ -276,7 +276,7 @@ PageType { onClicked: { questionDrawer.headerText = qsTr("Remove AmneziaWG from server?") - questionDrawer.descriptionText = qsTr("All users who you shared a connection with will no longer be able to connect to it.") + questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.") questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.noButtonText = qsTr("Cancel") diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index cd2dd51d..55cdcf04 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -365,7 +365,7 @@ PageType { onClicked: { questionDrawer.headerText = qsTr("Remove OpenVpn from server?") - questionDrawer.descriptionText = qsTr("All users who you shared a connection with will no longer be able to connect to it.") + questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.") questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.noButtonText = qsTr("Cancel") diff --git a/client/ui/qml/Pages2/PageProtocolRaw.qml b/client/ui/qml/Pages2/PageProtocolRaw.qml index f3621d96..967b605b 100644 --- a/client/ui/qml/Pages2/PageProtocolRaw.qml +++ b/client/ui/qml/Pages2/PageProtocolRaw.qml @@ -176,7 +176,7 @@ PageType { clickedFunction: function() { questionDrawer.headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getCurrentlyProcessedContainerName()) - questionDrawer.descriptionText = qsTr("All users who you shared a connection with will no longer be able to connect to it.") + questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.") questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.noButtonText = qsTr("Cancel") diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml index 30758da4..a961cf56 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -114,7 +114,7 @@ PageType { clickedFunction: function() { questionDrawer.headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getCurrentlyProcessedContainerName()) - questionDrawer.descriptionText = qsTr("All users who you shared a connection with will no longer be able to connect to it.") + questionDrawer.descriptionText = qsTr("All users with whom you shared a connection will no longer be able to connect to it.") questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.noButtonText = qsTr("Cancel") diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 25aad3de..ced7a5ff 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -118,7 +118,7 @@ PageType { Layout.fillWidth: true Layout.topMargin: 24 - headerText: qsTr("VPN Access") + headerText: qsTr("Share VPN Access") } Rectangle { @@ -171,8 +171,8 @@ PageType { Layout.topMargin: 24 Layout.bottomMargin: 24 - text: accessTypeSelector.currentIndex === 0 ? qsTr("VPN access without the ability to manage the server") : - qsTr("Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the server, as well as change settings.") + text: accessTypeSelector.currentIndex === 0 ? qsTr("Share VPN access without the ability to manage the server") : + qsTr("Share access to server management. The user with whom you share full access to the server will be able to add and remove any protocols and services to the server, as well as change settings.") color: "#878B91" } From 994aa327455c462a1c985b625a1f8d5f60fb7f92 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sun, 22 Oct 2023 17:31:13 +0500 Subject: [PATCH 275/278] added getting awg parameters when adding an already installed awg container --- client/configurators/awg_configurator.cpp | 4 +-- client/core/servercontroller.cpp | 28 +++++++++++++++++++ client/translations/amneziavpn_ru.ts | 10 +++++++ client/translations/amneziavpn_zh_CN.ts | 10 +++++++ client/ui/models/containers_model.cpp | 3 +- client/ui/models/containers_model.h | 2 +- .../qml/Pages2/PageSetupWizardInstalling.qml | 2 +- 7 files changed, 54 insertions(+), 5 deletions(-) diff --git a/client/configurators/awg_configurator.cpp b/client/configurators/awg_configurator.cpp index 8962067a..2bf03359 100644 --- a/client/configurators/awg_configurator.cpp +++ b/client/configurators/awg_configurator.cpp @@ -16,8 +16,6 @@ QString AwgConfigurator::genAwgConfig(const ServerCredentials &credentials, { QString config = WireguardConfigurator::genWireguardConfig(credentials, container, containerConfig, errorCode); - QJsonObject jsonConfig = QJsonDocument::fromJson(config.toUtf8()).object(); - ServerController serverController(m_settings); QString serverConfig = serverController.getTextFileFromContainer(container, credentials, protocols::awg::serverConfigPath, errorCode); @@ -45,6 +43,8 @@ QString AwgConfigurator::genAwgConfig(const ServerCredentials &credentials, config.replace("$UNDERLOAD_PACKET_MAGIC_HEADER", serverConfigMap.value(config_key::underloadPacketMagicHeader)); config.replace("$TRANSPORT_PACKET_MAGIC_HEADER", serverConfigMap.value(config_key::transportPacketMagicHeader)); + QJsonObject jsonConfig = QJsonDocument::fromJson(config.toUtf8()).object(); + jsonConfig[config_key::junkPacketCount] = serverConfigMap.value(config_key::junkPacketCount); jsonConfig[config_key::junkPacketMinSize] = serverConfigMap.value(config_key::junkPacketMinSize); jsonConfig[config_key::junkPacketMaxSize] = serverConfigMap.value(config_key::junkPacketMaxSize); diff --git a/client/core/servercontroller.cpp b/client/core/servercontroller.cpp index 443cd5a3..da76e1ff 100644 --- a/client/core/servercontroller.cpp +++ b/client/core/servercontroller.cpp @@ -834,6 +834,34 @@ ErrorCode ServerController::getAlreadyInstalledContainers(const ServerCredential containerConfig.insert(config_key::port, port); containerConfig.insert(config_key::transport_proto, transportProto); + if (protocol == Proto::Awg) { + QString serverConfig = getTextFileFromContainer(container, credentials, protocols::awg::serverConfigPath, &errorCode); + + QMap serverConfigMap; + auto serverConfigLines = serverConfig.split("\n"); + for (auto &line : serverConfigLines) { + auto trimmedLine = line.trimmed(); + if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) { + continue; + } else { + QStringList parts = trimmedLine.split(" = "); + if (parts.count() == 2) { + serverConfigMap.insert(parts[0].trimmed(), parts[1].trimmed()); + } + } + } + + containerConfig[config_key::junkPacketCount] = serverConfigMap.value(config_key::junkPacketCount); + containerConfig[config_key::junkPacketMinSize] = serverConfigMap.value(config_key::junkPacketMinSize); + containerConfig[config_key::junkPacketMaxSize] = serverConfigMap.value(config_key::junkPacketMaxSize); + containerConfig[config_key::initPacketJunkSize] = serverConfigMap.value(config_key::initPacketJunkSize); + containerConfig[config_key::responsePacketJunkSize] = serverConfigMap.value(config_key::responsePacketJunkSize); + containerConfig[config_key::initPacketMagicHeader] = serverConfigMap.value(config_key::initPacketMagicHeader); + containerConfig[config_key::responsePacketMagicHeader] = serverConfigMap.value(config_key::responsePacketMagicHeader); + containerConfig[config_key::underloadPacketMagicHeader] = serverConfigMap.value(config_key::underloadPacketMagicHeader); + containerConfig[config_key::transportPacketMagicHeader] = serverConfigMap.value(config_key::transportPacketMagicHeader); + } + config.insert(config_key::container, ContainerProps::containerToString(container)); } config.insert(ProtocolProps::protoToString(protocol), containerConfig); diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index f4ef9c0f..316cd076 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -2728,6 +2728,16 @@ This means that AmneziaWG keeps the fast performance of the original while addin error 0x%1: %2 error 0x%1: %2 + + + WireGuard Configuration Highlighter + + + + + &Randomize colors + + SelectLanguageDrawer diff --git a/client/translations/amneziavpn_zh_CN.ts b/client/translations/amneziavpn_zh_CN.ts index c711b3fc..592717ab 100644 --- a/client/translations/amneziavpn_zh_CN.ts +++ b/client/translations/amneziavpn_zh_CN.ts @@ -2867,6 +2867,16 @@ While it offers a blend of security, stability, and speed, it's essential t error 0x%1: %2 错误 0x%1: %2 + + + WireGuard Configuration Highlighter + + + + + &Randomize colors + + SelectLanguageDrawer diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index 780809c5..6a4c0e63 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -117,8 +117,9 @@ QString ContainersModel::getDefaultContainerName() return ContainerProps::containerHumanNames().value(m_defaultContainerIndex); } -void ContainersModel::setDefaultContainer(DockerContainer container) +void ContainersModel::setDefaultContainer(int index) { + auto container = static_cast(index); m_settings->setDefaultContainer(m_currentlyProcessedServerIndex, container); m_defaultContainerIndex = container; emit defaultContainerChanged(); diff --git a/client/ui/models/containers_model.h b/client/ui/models/containers_model.h index 4ba85749..997b21e3 100644 --- a/client/ui/models/containers_model.h +++ b/client/ui/models/containers_model.h @@ -46,7 +46,7 @@ public: public slots: DockerContainer getDefaultContainer(); QString getDefaultContainerName(); - void setDefaultContainer(DockerContainer container); + void setDefaultContainer(int index); void setCurrentlyProcessedServerIndex(const int index); diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index 50aad294..a223f646 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -25,7 +25,7 @@ PageType { function onInstallContainerFinished(finishedMessage, isServiceInstall) { if (!ConnectionController.isConnected && !isServiceInstall) { - ContainersModel.setDefaultContainer(ContainersModel.getCurrentlyProcessedContainerIndex) + ContainersModel.setDefaultContainer(ContainersModel.getCurrentlyProcessedContainerIndex()) } PageController.goToStartPage() From 7cfb38307e5533dbaddce6578e926a75430bc6ac Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Sun, 22 Oct 2023 18:04:34 +0500 Subject: [PATCH 276/278] removed re-processing of server config for awg --- client/configurators/awg_configurator.cpp | 42 ++++++++--------------- 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/client/configurators/awg_configurator.cpp b/client/configurators/awg_configurator.cpp index 2bf03359..c3e42258 100644 --- a/client/configurators/awg_configurator.cpp +++ b/client/configurators/awg_configurator.cpp @@ -16,44 +16,32 @@ QString AwgConfigurator::genAwgConfig(const ServerCredentials &credentials, { QString config = WireguardConfigurator::genWireguardConfig(credentials, container, containerConfig, errorCode); - ServerController serverController(m_settings); - QString serverConfig = serverController.getTextFileFromContainer(container, credentials, protocols::awg::serverConfigPath, errorCode); + QJsonObject jsonConfig = QJsonDocument::fromJson(config.toUtf8()).object(); + QString awgConfig = jsonConfig.value(config_key::config).toString(); - QMap serverConfigMap; - auto serverConfigLines = serverConfig.split("\n"); - for (auto &line : serverConfigLines) { + QMap configMap; + auto configLines = awgConfig.split("\n"); + for (auto &line : configLines) { auto trimmedLine = line.trimmed(); if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) { continue; } else { QStringList parts = trimmedLine.split(" = "); if (parts.count() == 2) { - serverConfigMap.insert(parts[0].trimmed(), parts[1].trimmed()); + configMap.insert(parts[0].trimmed(), parts[1].trimmed()); } } } - config.replace("$JUNK_PACKET_COUNT", serverConfigMap.value(config_key::junkPacketCount)); - config.replace("$JUNK_PACKET_MIN_SIZE", serverConfigMap.value(config_key::junkPacketMinSize)); - config.replace("$JUNK_PACKET_MAX_SIZE", serverConfigMap.value(config_key::junkPacketMaxSize)); - config.replace("$INIT_PACKET_JUNK_SIZE", serverConfigMap.value(config_key::initPacketJunkSize)); - config.replace("$RESPONSE_PACKET_JUNK_SIZE", serverConfigMap.value(config_key::responsePacketJunkSize)); - config.replace("$INIT_PACKET_MAGIC_HEADER", serverConfigMap.value(config_key::initPacketMagicHeader)); - config.replace("$RESPONSE_PACKET_MAGIC_HEADER", serverConfigMap.value(config_key::responsePacketMagicHeader)); - config.replace("$UNDERLOAD_PACKET_MAGIC_HEADER", serverConfigMap.value(config_key::underloadPacketMagicHeader)); - config.replace("$TRANSPORT_PACKET_MAGIC_HEADER", serverConfigMap.value(config_key::transportPacketMagicHeader)); - - QJsonObject jsonConfig = QJsonDocument::fromJson(config.toUtf8()).object(); - - jsonConfig[config_key::junkPacketCount] = serverConfigMap.value(config_key::junkPacketCount); - jsonConfig[config_key::junkPacketMinSize] = serverConfigMap.value(config_key::junkPacketMinSize); - jsonConfig[config_key::junkPacketMaxSize] = serverConfigMap.value(config_key::junkPacketMaxSize); - jsonConfig[config_key::initPacketJunkSize] = serverConfigMap.value(config_key::initPacketJunkSize); - jsonConfig[config_key::responsePacketJunkSize] = serverConfigMap.value(config_key::responsePacketJunkSize); - jsonConfig[config_key::initPacketMagicHeader] = serverConfigMap.value(config_key::initPacketMagicHeader); - jsonConfig[config_key::responsePacketMagicHeader] = serverConfigMap.value(config_key::responsePacketMagicHeader); - jsonConfig[config_key::underloadPacketMagicHeader] = serverConfigMap.value(config_key::underloadPacketMagicHeader); - jsonConfig[config_key::transportPacketMagicHeader] = serverConfigMap.value(config_key::transportPacketMagicHeader); + jsonConfig[config_key::junkPacketCount] = configMap.value(config_key::junkPacketCount); + jsonConfig[config_key::junkPacketMinSize] = configMap.value(config_key::junkPacketMinSize); + jsonConfig[config_key::junkPacketMaxSize] = configMap.value(config_key::junkPacketMaxSize); + jsonConfig[config_key::initPacketJunkSize] = configMap.value(config_key::initPacketJunkSize); + jsonConfig[config_key::responsePacketJunkSize] = configMap.value(config_key::responsePacketJunkSize); + jsonConfig[config_key::initPacketMagicHeader] = configMap.value(config_key::initPacketMagicHeader); + jsonConfig[config_key::responsePacketMagicHeader] = configMap.value(config_key::responsePacketMagicHeader); + jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader); + jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader); return QJsonDocument(jsonConfig).toJson(); } From 97090888d5b2f10c900464db1120a29bee034967 Mon Sep 17 00:00:00 2001 From: pokamest Date: Sun, 22 Oct 2023 08:11:37 -0700 Subject: [PATCH 277/278] Bump version --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 12fc8dce..2e7be435 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.0.8.5 +project(${PROJECT} VERSION 4.0.8.6 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) From e749cc7578d0df83a8bc91f97b7143c36b6b40ea Mon Sep 17 00:00:00 2001 From: pokamest Date: Mon, 23 Oct 2023 20:32:28 +0100 Subject: [PATCH 278/278] Update amneziavpn_ru.ts Typo fix --- client/translations/amneziavpn_ru.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index f94e0514..cfa33040 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -1332,7 +1332,7 @@ Already installed containers were found on the server. All installed containers Clear Amnezia cache - Очистить кэш Amnezia на сервере + Очистить кэш Amnezia