diff --git a/client/core/controllers/selfhosted/installController.cpp b/client/core/controllers/selfhosted/installController.cpp index ddfbcdf47..ff59ca99f 100644 --- a/client/core/controllers/selfhosted/installController.cpp +++ b/client/core/controllers/selfhosted/installController.cpp @@ -119,9 +119,14 @@ ErrorCode InstallController::setupContainer(const ServerCredentials &credentials return e; qDebug().noquote() << "InstallController::setupContainer prepareHostWorker finished"; + amnezia::ScriptVars removeContainerVars = + amnezia::genBaseVars(credentials, container, QString(), QString()); + if (!isUpdate) { + removeContainerVars.append({ { "$REMOVE_CONTAINER_DATA", QStringLiteral("1") } }); + } sshSession.runScript(credentials, - sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container), - amnezia::genBaseVars(credentials, container, QString(), QString()))); + sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container), + removeContainerVars)); qDebug().noquote() << "InstallController::setupContainer removeContainer finished"; qDebug().noquote() << "buildContainerWorker start"; @@ -942,10 +947,12 @@ ErrorCode InstallController::removeContainer(const QString &serverId, DockerCont return ErrorCode::InternalError; } SshSession sshSession(this); + amnezia::ScriptVars removeContainerVars = + amnezia::genBaseVars(credentials, container, QString(), QString()); + removeContainerVars.append({ { "$REMOVE_CONTAINER_DATA", QStringLiteral("1") } }); ErrorCode errorCode = sshSession.runScript( credentials, - sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container), - amnezia::genBaseVars(credentials, container, QString(), QString()))); + sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container), removeContainerVars)); if (errorCode == ErrorCode::NoError) { QMap containers = adminConfig->containers; diff --git a/client/core/utils/selfhosted/scriptsRegistry.cpp b/client/core/utils/selfhosted/scriptsRegistry.cpp index 14c43eaab..4e07ae752 100644 --- a/client/core/utils/selfhosted/scriptsRegistry.cpp +++ b/client/core/utils/selfhosted/scriptsRegistry.cpp @@ -295,6 +295,8 @@ amnezia::ScriptVars amnezia::genMtProxyVars(const ContainerConfig &containerConf vars.append({{"$MTPROXY_PORT", c.port.isEmpty() ? QString(protocols::mtProxy::defaultPort) : c.port}}); vars.append({{"$MTPROXY_SECRET", c.secret}}); + vars.append({{"$MTPROXY_REGENERATE_SECRET", + c.secret.isEmpty() ? QStringLiteral("1") : QStringLiteral("0")}}); vars.append({{"$MTPROXY_TAG", c.tag}}); vars.append({{"$MTPROXY_TRANSPORT_MODE", c.transportMode.isEmpty() ? QString(protocols::mtProxy::transportModeStandard) @@ -350,6 +352,8 @@ amnezia::ScriptVars amnezia::genTelemtVars(const ContainerConfig &containerConfi vars.append({ { "$TELEMT_TOML_TLS", faketls ? QLatin1String("true") : QLatin1String("false") } }); vars.append({ { "$TELEMT_PORT", c.port.isEmpty() ? QString(protocols::telemt::defaultPort) : c.port } }); vars.append({ { "$TELEMT_SECRET", c.secret } }); + vars.append({ { "$TELEMT_REGENERATE_SECRET", + c.secret.isEmpty() ? QStringLiteral("1") : QStringLiteral("0") } }); vars.append({ { "$TELEMT_TAG", c.tag } }); QString tlsDomain = c.tlsDomain; if (tlsDomain.isEmpty()) { diff --git a/client/server_scripts/mtproxy/configure_container.sh b/client/server_scripts/mtproxy/configure_container.sh index 5ba6da11b..16d4e3357 100644 --- a/client/server_scripts/mtproxy/configure_container.sh +++ b/client/server_scripts/mtproxy/configure_container.sh @@ -4,8 +4,10 @@ curl -s https://core.telegram.org/getProxySecret -o /data/proxy-secret curl -s https://core.telegram.org/getProxyConfig -o /data/proxy-multi.conf -# Determine secret: env var -> saved file -> generate new -if [ -n "$MTPROXY_SECRET" ]; then +# Determine secret: regenerate (fresh install) -> env var -> saved file -> generate new +if [ "$MTPROXY_REGENERATE_SECRET" = "1" ]; then + SECRET=$(openssl rand -hex 16) +elif [ -n "$MTPROXY_SECRET" ]; then SECRET="$MTPROXY_SECRET" elif [ -f /data/secret ]; then SECRET=$(cat /data/secret) diff --git a/client/server_scripts/remove_container.sh b/client/server_scripts/remove_container.sh index 3e894e8f4..76e64c051 100644 --- a/client/server_scripts/remove_container.sh +++ b/client/server_scripts/remove_container.sh @@ -1,3 +1,4 @@ sudo docker stop $CONTAINER_NAME;\ sudo docker rm -fv $CONTAINER_NAME;\ -sudo docker rmi $CONTAINER_NAME +sudo docker rmi $CONTAINER_NAME;\ +test "$REMOVE_CONTAINER_DATA" = "1" && sudo docker volume rm -f ${CONTAINER_NAME}-data 2>/dev/null || true diff --git a/client/server_scripts/telemt/configure_container.sh b/client/server_scripts/telemt/configure_container.sh index 6cd9d31db..8cfcf5f77 100644 --- a/client/server_scripts/telemt/configure_container.sh +++ b/client/server_scripts/telemt/configure_container.sh @@ -4,8 +4,10 @@ echo "[*] Amnezia Telemt: configure script start" mkdir -p /data/tlsfront -# Secret: substituted $TELEMT_SECRET -> saved file -> openssl (same rules as MTProxy configure) -if [ -n "$TELEMT_SECRET" ]; then +# Secret: regenerate (fresh install) -> env var -> saved file -> openssl +if [ "$TELEMT_REGENERATE_SECRET" = "1" ]; then + SECRET=$(openssl rand -hex 16) +elif [ -n "$TELEMT_SECRET" ]; then SECRET="$TELEMT_SECRET" elif [ -f /data/secret ]; then SECRET=$(cat /data/secret) diff --git a/client/ui/qml/Components/SettingsContainersListView.qml b/client/ui/qml/Components/SettingsContainersListView.qml index 34d375767..985f8ed49 100644 --- a/client/ui/qml/Components/SettingsContainersListView.qml +++ b/client/ui/qml/Components/SettingsContainersListView.qml @@ -47,10 +47,10 @@ ListViewType { PageController.goToPage(PageEnum.PageServiceDnsSettings) } else if (isMtProxy) { MtProxyConfigModel.updateModel(config) - PageController.goToPage(PageEnum.PageServiceMtProxySettings) + PageController.goToPage(PageEnum.PageServiceMtProxySettings, false) } else if (isTelemt) { TelemtConfigModel.updateModel(config) - PageController.goToPage(PageEnum.PageServiceTelemtSettings) + PageController.goToPage(PageEnum.PageServiceTelemtSettings, false) } else { InstallController.updateProtocols(ServersUiController.processedServerId, containerIndex) PageController.goToPage(PageEnum.PageSettingsServerProtocol) diff --git a/client/ui/qml/Controls2/BaseHeaderType.qml b/client/ui/qml/Controls2/BaseHeaderType.qml index f46a925a3..0e9ccff7f 100644 --- a/client/ui/qml/Controls2/BaseHeaderType.qml +++ b/client/ui/qml/Controls2/BaseHeaderType.qml @@ -12,6 +12,8 @@ Item { property int headerTextMaximumLineCount: 2 property int headerTextElide: Qt.ElideRight property string descriptionText + property string descriptionLinkText + property string descriptionLinkUrl property alias headerRow: headerRow implicitWidth: content.implicitWidth @@ -43,5 +45,26 @@ Item { color: AmneziaStyle.color.mutedGray visible: root.descriptionText !== "" } + + ParagraphTextType { + id: descriptionLink + Layout.topMargin: 16 + Layout.fillWidth: true + text: root.descriptionLinkText !== "" && root.descriptionLinkUrl !== "" + ? ("" + root.descriptionLinkText + "") + : "" + textFormat: Text.RichText + visible: root.descriptionLinkText !== "" + + onLinkActivated: function(link) { + Qt.openUrlExternally(link) + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.NoButton + cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor + } + } } } diff --git a/client/ui/qml/Pages2/PageServiceMtProxySettings.qml b/client/ui/qml/Pages2/PageServiceMtProxySettings.qml index 5041184b9..57284fcca 100644 --- a/client/ui/qml/Pages2/PageServiceMtProxySettings.qml +++ b/client/ui/qml/Pages2/PageServiceMtProxySettings.qml @@ -20,15 +20,10 @@ import "../Components" PageType { id: root - Rectangle { - anchors.fill: parent - z: -1 - color: AmneziaStyle.color.onyxBlack - } - property int containerStatus: 1 property bool isUpdating: false property bool isCheckingStatus: false + property bool isFetchingSecret: false property bool previousEnabled: true property int previousContainerStatus: 1 @@ -50,7 +45,7 @@ PageType { onSavedTransportModeChanged: { if (savedTransportMode === "faketls") { - root.syncedSecretTabIndex = 2 + root.syncedSecretTabIndex = 1 } else if (savedTransportMode !== "") { root.syncedSecretTabIndex = 0 } @@ -68,9 +63,96 @@ PageType { readonly property bool mtProxyNetworkBlocked: !NetworkReachabilityController.hasInternetAccess - readonly property bool navigationBlockedWhileBusy: isUpdating || diagLoading + property bool remoteOperationBusy: false + readonly property bool operationInProgress: isCheckingStatus || isFetchingSecret || isUpdating || diagLoading + readonly property bool pageBusy: operationInProgress || remoteOperationBusy + readonly property bool navigationBlockedWhileBusy: pageBusy + + property bool pageOpenHandled: false + property bool busyIndicatorShown: false + + function syncPageBusyIndicator() { + if (!root.pageOpenHandled) { + return + } + var wantBusy = root.pageBusy + if (wantBusy === root.busyIndicatorShown) { + return + } + root.busyIndicatorShown = wantBusy + PageController.showBusyIndicator(wantBusy) + } + + onPageBusyChanged: syncPageBusyIndicator() + + function mtProxyDomainToHex(domain) { + var hex = "" + for (var i = 0; i < domain.length; i++) { + var code = domain.charCodeAt(i).toString(16) + hex += (code.length < 2 ? "0" : "") + code + } + return hex + } + + function mtProxyClientSecret(baseHex32, mode, tlsDomain) { + if (baseHex32 === "") { + return "" + } + if (mode === "faketls") { + return "ee" + baseHex32 + mtProxyDomainToHex(tlsDomain) + } + return "dd" + baseHex32 + } + + function mtProxyClientSecretForTabIndex(baseHex32, tabIndex, tlsDomain, defaultTlsDomain) { + var domain = tlsDomain !== "" ? tlsDomain : defaultTlsDomain + if (tabIndex === 1) { + return mtProxyClientSecret(baseHex32, "faketls", domain) + } + return mtProxyClientSecret(baseHex32, "standard", domain) + } + + property bool containerStatusRefreshCallPending: false + + function mtProxyRequestContainerStatusRefresh() { + if (!NetworkReachabilityController.hasInternetAccess) { + isCheckingStatus = false + syncPageBusyIndicator() + return + } + isCheckingStatus = true + syncPageBusyIndicator() + InstallController.refreshContainerStatus(ServersUiController.processedServerId, ServersUiController.processedContainerIndex) + } + + function mtProxyScheduleContainerStatusRefresh() { + if (containerStatusRefreshCallPending) { + return + } + containerStatusRefreshCallPending = true + Qt.callLater(function () { + containerStatusRefreshCallPending = false + root.mtProxyRequestContainerStatusRefresh() + }) + } + + function mtProxyOnPageShown() { + if (root.pageOpenHandled) { + return + } + root.pageOpenHandled = true + + PageController.disableControls(navigationBlockedWhileBusy) + + if (!NetworkReachabilityController.hasInternetAccess) { + isCheckingStatus = false + } else { + isCheckingStatus = true + } + syncPageBusyIndicator() + root.mtProxyScheduleContainerStatusRefresh() + } - // Hex values that exist in last loaded / last successfully saved config — show link panel only for these. property var mtProxyPersistedAdditionalHex: [] function mtProxyRefreshPersistedAdditionalSecrets() { @@ -92,11 +174,8 @@ PageType { return false } - // Rejects garbage like "123123123123"; only dotted IPv4 shape (≤3 digits per octet, ≤4 octets). readonly property var natIpv4InputFormat: /^(\d{1,3}\.){0,3}\d{0,3}$/ - // Defer SSH/updateContainer so QML control handlers return before nested event loops run; - // avoids "Object destroyed while one of its QML signal handlers is in progress". function mtProxyScheduleUpdate(closePage) { var cp = closePage === undefined ? false : closePage Qt.callLater(function () { @@ -104,7 +183,6 @@ PageType { }) } - // Optional IPv4: show invalid while typing only when the string looks complete (four octets), so partial entry is not nagged. function natIpv4FieldShowInvalidError(text) { var t = text ? String(text).replace(/^\s+|\s+$/g, '') : "" if (t === "") @@ -167,15 +245,9 @@ PageType { root.mtProxyRefreshPersistedAdditionalSecrets() }) - if (!NetworkReachabilityController.hasInternetAccess) { - isCheckingStatus = false - return - } - isCheckingStatus = true - InstallController.refreshContainerStatus(ServersUiController.processedServerId, ServersUiController.processedContainerIndex) + Qt.callLater(root.mtProxyOnPageShown) } - // Block back navigation and Escape (via PageStart.isControlsDisabled) while SSH/update or diagnostics refresh runs. onNavigationBlockedWhileBusyChanged: { if (root.visible) { PageController.disableControls(navigationBlockedWhileBusy) @@ -184,10 +256,16 @@ PageType { onVisibleChanged: { if (!visible) { + root.pageOpenHandled = false + containerStatusRefreshCallPending = false + isCheckingStatus = false + isFetchingSecret = false + busyIndicatorShown = false PageController.disableControls(false) + PageController.showBusyIndicator(false) diagLoading = false } else { - PageController.disableControls(navigationBlockedWhileBusy) + root.mtProxyOnPageShown() } } @@ -199,8 +277,7 @@ PageType { return } if (NetworkReachabilityController.hasInternetAccess) { - isCheckingStatus = true - InstallController.refreshContainerStatus(ServersUiController.processedServerId, ServersUiController.processedContainerIndex) + root.mtProxyScheduleContainerStatusRefresh() } } } @@ -208,10 +285,15 @@ PageType { Connections { target: InstallController + function onServerIsBusy(busy) { + remoteOperationBusy = busy + } + function onUpdateContainerFinished(message, closePage) { if (!root.visible) { isUpdating = false isCheckingStatus = false + isFetchingSecret = false return } isUpdating = false @@ -227,9 +309,11 @@ PageType { if (!root.visible) { isUpdating = false isCheckingStatus = false + isFetchingSecret = false return } isUpdating = false + isFetchingSecret = false containerStatus = previousContainerStatus MtProxyConfigModel.setEnabled(previousEnabled) MtProxyConfigModel.setPort(previousPort) @@ -254,6 +338,7 @@ PageType { } if (enabled && pendingUpdateAfterEnable) { pendingUpdateAfterEnable = false + isUpdating = true root.mtProxyScheduleUpdate(false) return } @@ -266,9 +351,9 @@ PageType { function onContainerStatusRefreshed(status) { if (!root.visible) { isCheckingStatus = false + isFetchingSecret = false return } - isCheckingStatus = false containerStatus = status root.savedTransportMode = MtProxyConfigModel.getTransportMode() @@ -276,10 +361,17 @@ PageType { root.savedPublicHost = MtProxyConfigModel.getPublicHost() if (status === 1) { MtProxyConfigModel.setEnabled(true) + isFetchingSecret = true + isCheckingStatus = false InstallController.fetchContainerSecret(ServersUiController.processedServerId, ServersUiController.processedContainerIndex) - } else if (status === 2) { - MtProxyConfigModel.setEnabled(false) + } else { + isFetchingSecret = false + isCheckingStatus = false + if (status === 2) { + MtProxyConfigModel.setEnabled(false) + } } + syncPageBusyIndicator() } function onContainerDiagnosticsRefreshed(portReachable, upstreamReachable, clientsConnected, lastConfigRefresh, statsEndpoint) { @@ -296,20 +388,35 @@ PageType { function onContainerSecretFetched(secret) { if (!root.visible) { + isFetchingSecret = false return } + isFetchingSecret = false + syncPageBusyIndicator() MtProxyConfigModel.validateAndSetSecret(secret) } } + Item { + id: contentLayer + anchors.fill: parent + enabled: !root.pageBusy + BackButtonType { id: backButton anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin + onFocusChanged: { - if (this.activeFocus) connectionListView.positionViewAtBeginning() + if (this.activeFocus) { + if (mainTabBar.currentIndex === 0) { + connectionListView.positionViewAtBeginning() + } else { + settingsListView.positionViewAtBeginning() + } + } } } @@ -318,57 +425,62 @@ PageType { anchors.top: backButton.bottom anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 8 spacing: 0 BaseHeaderType { Layout.fillWidth: true Layout.leftMargin: 16 Layout.rightMargin: 16 - Layout.topMargin: 8 + Layout.bottomMargin: 24 + headerText: qsTr("MTProxy settings") + descriptionLinkText: qsTr("Read more about this settings") + descriptionLinkUrl: "https://core.telegram.org/proxy" } - LabelWithButtonType { + CaptionTextType { Layout.fillWidth: true - Layout.leftMargin: 0 + Layout.leftMargin: 16 Layout.rightMargin: 16 - text: qsTr("Read more about this settings") - textColor: AmneziaStyle.color.goldenApricot - clickedFunction: function () { - Qt.openUrlExternally("https://core.telegram.org/proxy") + Layout.topMargin: 8 + visible: root.mtProxyNetworkBlocked + text: qsTr("No internet connection. Connect to the internet to change MTProxy settings.") + color: AmneziaStyle.color.mutedGray + wrapMode: Text.WordWrap + font.pixelSize: 14 + } + } + + TabBar { + id: mainTabBar + anchors.top: pageHeader.bottom + anchors.left: parent.left + anchors.right: parent.right + width: parent.width + + background: Rectangle { + color: AmneziaStyle.color.transparent + Rectangle { + width: parent.width + height: 1 + anchors.bottom: parent.bottom + color: AmneziaStyle.color.slateGray } } - TabBar { - id: mainTabBar - Layout.fillWidth: true - Layout.topMargin: 4 - - background: Rectangle { - color: AmneziaStyle.color.transparent - Rectangle { - width: parent.width - height: 1 - anchors.bottom: parent.bottom - color: AmneziaStyle.color.slateGray - } - } - - TabButtonType { - text: qsTr("Connection") - isSelected: mainTabBar.currentIndex === 0 - } - TabButtonType { - text: qsTr("Settings") - isSelected: mainTabBar.currentIndex === 1 - } + TabButtonType { + text: qsTr("Connection") + isSelected: mainTabBar.currentIndex === 0 + } + TabButtonType { + text: qsTr("Settings") + isSelected: mainTabBar.currentIndex === 1 } } StackLayout { id: tabContent - anchors.top: pageHeader.bottom + anchors.top: mainTabBar.bottom anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: parent.right @@ -382,35 +494,11 @@ PageType { width: connectionListView.width spacing: 0 - function domainToHex(domain) { - var hex = "" - for (var i = 0; i < domain.length; i++) { - var code = domain.charCodeAt(i).toString(16) - hex += (code.length < 2 ? "0" : "") + code - } - return hex - } - - function secretForMode(mode) { - if (mode === "faketls") { - var domain = root.savedTlsDomain !== "" ? root.savedTlsDomain : MtProxyConfigModel.defaultTlsDomain() - return "ee" + secret + domainToHex(domain) - } else if (mode === "padded") { - return "dd" + secret - } - return secret - } - property int secretTabIndex: root.syncedSecretTabIndex function activeSecret() { - if (root.syncedSecretTabIndex === 0) { - return secretForMode("standard") - } - if (root.syncedSecretTabIndex === 1) { - return secretForMode("padded") - } - return secretForMode("faketls") + return root.mtProxyClientSecretForTabIndex(secret, root.syncedSecretTabIndex, + root.savedTlsDomain, MtProxyConfigModel.defaultTlsDomain()) } function effectiveSecret() { @@ -754,33 +842,9 @@ PageType { width: settingsListView.width spacing: 0 - function mtProxyDomainToHex(domain) { - var hex = "" - for (var i = 0; i < domain.length; i++) { - var code = domain.charCodeAt(i).toString(16) - hex += (code.length < 2 ? "0" : "") + code - } - return hex - } - - function mtProxySecretForBaseHex(baseHex, mode) { - if (mode === "faketls") { - var domain = root.savedTlsDomain !== "" ? root.savedTlsDomain : MtProxyConfigModel.defaultTlsDomain() - return "ee" + baseHex + mtProxyDomainToHex(domain) - } else if (mode === "padded") { - return "dd" + baseHex - } - return baseHex - } - function mtProxyActiveSecretForBaseHex(baseHex) { - if (root.syncedSecretTabIndex === 0) { - return mtProxySecretForBaseHex(baseHex, "standard") - } - if (root.syncedSecretTabIndex === 1) { - return mtProxySecretForBaseHex(baseHex, "padded") - } - return mtProxySecretForBaseHex(baseHex, "faketls") + return root.mtProxyClientSecretForTabIndex(baseHex, root.syncedSecretTabIndex, + root.savedTlsDomain, MtProxyConfigModel.defaultTlsDomain()) } function mtProxyEffectiveHostForLinks() { @@ -804,7 +868,7 @@ PageType { Layout.bottomMargin: 16 text: qsTr("Enable MTProxy") checked: isEnabled - enabled: !isCheckingStatus && containerStatus !== 0 && containerStatus !== 3 && !isUpdating + enabled: containerStatus !== 0 && containerStatus !== 3 && !root.pageBusy && !root.mtProxyNetworkBlocked onToggled: function () { if (checked !== isEnabled) { @@ -843,13 +907,14 @@ PageType { CaptionTextType { Layout.fillWidth: true - text: secret !== "" ? secret : qsTr("Not generated") + text: secret !== "" ? mtProxyActiveSecretForBaseHex(secret) : qsTr("Not generated") color: secret !== "" ? AmneziaStyle.color.paleGray : AmneziaStyle.color.mutedGray - elide: Text.ElideMiddle + wrapMode: Text.WrapAnywhere font.pixelSize: 14 } ImageButtonType { + Layout.alignment: Qt.AlignTop implicitWidth: 36 implicitHeight: 36 hoverEnabled: true @@ -1098,6 +1163,7 @@ PageType { clickedFunction: function () { transportMode = (index === 0) ? "standard" : "faketls" MtProxyConfigModel.setTransportMode(transportMode) + root.syncedSecretTabIndex = transportMode === "faketls" ? 1 : 0 transportModeDropDown.closeTriggered() } } @@ -1286,6 +1352,9 @@ PageType { imageColor: AmneziaStyle.color.vibrantRed onClicked: { MtProxyConfigModel.removeAdditionalSecret(index) + if (containerStatus === 1) { + root.mtProxyScheduleUpdate(false) + } } } } @@ -1852,34 +1921,5 @@ PageType { } } - Rectangle { - anchors.fill: parent - visible: isCheckingStatus || isUpdating || root.mtProxyNetworkBlocked - color: AmneziaStyle.color.midnightBlack - opacity: 0.6 - z: 1 - MouseArea { - anchors.fill: parent - } - BusyIndicator { - anchors.centerIn: parent - visible: isCheckingStatus || isUpdating - running: isCheckingStatus || isUpdating - width: 48 - height: 48 - } - CaptionTextType { - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 24 - anchors.rightMargin: 24 - visible: root.mtProxyNetworkBlocked && !isCheckingStatus && !isUpdating - horizontalAlignment: Text.AlignHCenter - text: qsTr("No internet connection. Connect to the internet to change MTProxy settings.") - color: AmneziaStyle.color.paleGray - wrapMode: Text.WordWrap - font.pixelSize: 14 - } } } diff --git a/client/ui/qml/Pages2/PageServiceTelemtSettings.qml b/client/ui/qml/Pages2/PageServiceTelemtSettings.qml index 4d7648b8f..d97fd70d0 100644 --- a/client/ui/qml/Pages2/PageServiceTelemtSettings.qml +++ b/client/ui/qml/Pages2/PageServiceTelemtSettings.qml @@ -18,15 +18,10 @@ import "../Components" PageType { id: root - Rectangle { - anchors.fill: parent - z: -1 - color: AmneziaStyle.color.onyxBlack - } - property int containerStatus: 1 property bool isUpdating: false property bool isCheckingStatus: false + property bool isFetchingSecret: false property bool previousEnabled: true property int previousContainerStatus: 1 @@ -40,6 +35,7 @@ PageType { property bool previousNatEnabled: false property string previousNatInternalIp: "" property string previousNatExternalIp: "" + property string previousSecret: "" property string savedTransportMode: "" property string savedTlsDomain: "" @@ -47,7 +43,7 @@ PageType { onSavedTransportModeChanged: { if (savedTransportMode === "faketls") { - root.syncedSecretTabIndex = 2 + root.syncedSecretTabIndex = 1 } else if (savedTransportMode !== "") { root.syncedSecretTabIndex = 0 } @@ -64,9 +60,97 @@ PageType { property string diagStatsEndpoint: "" readonly property bool telemtNetworkBlocked: !NetworkReachabilityController.hasInternetAccess - readonly property bool navigationBlockedWhileBusy: isUpdating || diagLoading - // Defer SSH/updateContainer so QML control handlers return before nested event loops run. + property bool remoteOperationBusy: false + readonly property bool operationInProgress: isCheckingStatus || isFetchingSecret || isUpdating || diagLoading + readonly property bool pageBusy: operationInProgress || remoteOperationBusy + readonly property bool navigationBlockedWhileBusy: pageBusy + + property bool pageOpenHandled: false + property bool busyIndicatorShown: false + + function syncPageBusyIndicator() { + if (!root.pageOpenHandled) { + return + } + var wantBusy = root.pageBusy + if (wantBusy === root.busyIndicatorShown) { + return + } + root.busyIndicatorShown = wantBusy + PageController.showBusyIndicator(wantBusy) + } + + onPageBusyChanged: syncPageBusyIndicator() + + function telemtDomainToHex(domain) { + var hex = "" + for (var i = 0; i < domain.length; i++) { + var code = domain.charCodeAt(i).toString(16) + hex += (code.length < 2 ? "0" : "") + code + } + return hex + } + + function telemtClientSecret(baseHex32, mode, tlsDomain) { + if (baseHex32 === "") { + return "" + } + if (mode === "faketls") { + return "ee" + baseHex32 + telemtDomainToHex(tlsDomain) + } + return "dd" + baseHex32 + } + + function telemtClientSecretForTabIndex(baseHex32, tabIndex, tlsDomain, defaultTlsDomain) { + var domain = tlsDomain !== "" ? tlsDomain : defaultTlsDomain + if (tabIndex === 1) { + return telemtClientSecret(baseHex32, "faketls", domain) + } + return telemtClientSecret(baseHex32, "standard", domain) + } + + property bool containerStatusRefreshCallPending: false + + function telemtRequestContainerStatusRefresh() { + if (!NetworkReachabilityController.hasInternetAccess) { + isCheckingStatus = false + syncPageBusyIndicator() + return + } + isCheckingStatus = true + syncPageBusyIndicator() + InstallController.refreshContainerStatus(ServersUiController.processedServerId, ServersUiController.processedContainerIndex) + } + + function telemtScheduleContainerStatusRefresh() { + if (containerStatusRefreshCallPending) { + return + } + containerStatusRefreshCallPending = true + Qt.callLater(function () { + containerStatusRefreshCallPending = false + root.telemtRequestContainerStatusRefresh() + }) + } + + function telemtOnPageShown() { + if (root.pageOpenHandled) { + return + } + root.pageOpenHandled = true + + PageController.disableControls(navigationBlockedWhileBusy) + + if (!NetworkReachabilityController.hasInternetAccess) { + isCheckingStatus = false + } else { + isCheckingStatus = true + } + syncPageBusyIndicator() + root.telemtScheduleContainerStatusRefresh() + } + function telemtScheduleUpdate(closePage) { var cp = closePage === undefined ? false : closePage Qt.callLater(function () { @@ -105,12 +189,7 @@ PageType { root.savedTlsDomain = TelemtConfigModel.getTlsDomain() root.savedPublicHost = TelemtConfigModel.getPublicHost() - if (!NetworkReachabilityController.hasInternetAccess) { - isCheckingStatus = false - return - } - isCheckingStatus = true - InstallController.refreshContainerStatus(ServersUiController.processedServerId, ServersUiController.processedContainerIndex) + Qt.callLater(root.telemtOnPageShown) } onNavigationBlockedWhileBusyChanged: { @@ -121,10 +200,16 @@ PageType { onVisibleChanged: { if (!visible) { + root.pageOpenHandled = false + containerStatusRefreshCallPending = false + isCheckingStatus = false + isFetchingSecret = false + busyIndicatorShown = false PageController.disableControls(false) + PageController.showBusyIndicator(false) diagLoading = false } else { - PageController.disableControls(navigationBlockedWhileBusy) + root.telemtOnPageShown() } } @@ -136,8 +221,7 @@ PageType { return } if (NetworkReachabilityController.hasInternetAccess) { - isCheckingStatus = true - InstallController.refreshContainerStatus(ServersUiController.processedServerId, ServersUiController.processedContainerIndex) + root.telemtScheduleContainerStatusRefresh() } } } @@ -145,10 +229,15 @@ PageType { Connections { target: InstallController + function onServerIsBusy(busy) { + remoteOperationBusy = busy + } + function onUpdateContainerFinished(message, closePage) { if (!root.visible) { isUpdating = false isCheckingStatus = false + isFetchingSecret = false return } isUpdating = false @@ -166,9 +255,11 @@ PageType { if (!root.visible) { isUpdating = false isCheckingStatus = false + isFetchingSecret = false return } isUpdating = false + isFetchingSecret = false containerStatus = previousContainerStatus TelemtConfigModel.setEnabled(previousEnabled) TelemtConfigModel.setPort(previousPort) @@ -181,6 +272,9 @@ PageType { TelemtConfigModel.setNatEnabled(previousNatEnabled) TelemtConfigModel.setNatInternalIp(previousNatInternalIp) TelemtConfigModel.setNatExternalIp(previousNatExternalIp) + if (previousSecret !== "") { + TelemtConfigModel.setSecret(previousSecret) + } } function onSetContainerEnabledFinished(enabled) { @@ -190,6 +284,7 @@ PageType { } if (enabled && pendingUpdateAfterEnable) { pendingUpdateAfterEnable = false + isUpdating = true root.telemtScheduleUpdate(false) return } @@ -202,9 +297,9 @@ PageType { function onContainerStatusRefreshed(status) { if (!root.visible) { isCheckingStatus = false + isFetchingSecret = false return } - isCheckingStatus = false containerStatus = status root.savedTransportMode = TelemtConfigModel.getTransportMode() @@ -212,10 +307,17 @@ PageType { root.savedPublicHost = TelemtConfigModel.getPublicHost() if (status === 1) { TelemtConfigModel.setEnabled(true) + isFetchingSecret = true + isCheckingStatus = false InstallController.fetchContainerSecret(ServersUiController.processedServerId, ServersUiController.processedContainerIndex) - } else if (status === 2) { - TelemtConfigModel.setEnabled(false) + } else { + isFetchingSecret = false + isCheckingStatus = false + if (status === 2) { + TelemtConfigModel.setEnabled(false) + } } + syncPageBusyIndicator() } function onContainerDiagnosticsRefreshed(portReachable, upstreamReachable, clientsConnected, lastConfigRefresh, statsEndpoint) { @@ -232,20 +334,35 @@ PageType { function onContainerSecretFetched(secret) { if (!root.visible) { + isFetchingSecret = false return } + isFetchingSecret = false + syncPageBusyIndicator() TelemtConfigModel.validateAndSetSecret(secret) } } + Item { + id: contentLayer + anchors.fill: parent + enabled: !root.pageBusy + BackButtonType { id: backButton anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 20 + SettingsController.safeAreaTopMargin + anchors.topMargin: 20 + PageController.safeAreaTopMargin + onFocusChanged: { - if (this.activeFocus) connectionListView.positionViewAtBeginning() + if (this.activeFocus) { + if (mainTabBar.currentIndex === 0) { + connectionListView.positionViewAtBeginning() + } else { + settingsListView.positionViewAtBeginning() + } + } } } @@ -254,57 +371,62 @@ PageType { anchors.top: backButton.bottom anchors.left: parent.left anchors.right: parent.right - anchors.topMargin: 8 spacing: 0 BaseHeaderType { Layout.fillWidth: true Layout.leftMargin: 16 Layout.rightMargin: 16 - Layout.topMargin: 8 + Layout.bottomMargin: 24 + headerText: qsTr("Telemt settings") + descriptionLinkText: qsTr("Read more about this settings") + descriptionLinkUrl: "https://github.com/telemt/telemt" } - LabelWithButtonType { + CaptionTextType { Layout.fillWidth: true - Layout.leftMargin: 0 + Layout.leftMargin: 16 Layout.rightMargin: 16 - text: qsTr("Read more about this settings") - textColor: AmneziaStyle.color.goldenApricot - clickedFunction: function () { - Qt.openUrlExternally("https://github.com/telemt/telemt") + Layout.topMargin: 8 + visible: root.telemtNetworkBlocked + text: qsTr("No internet connection. Connect to the internet to change Telemt settings.") + color: AmneziaStyle.color.mutedGray + wrapMode: Text.WordWrap + font.pixelSize: 14 + } + } + + TabBar { + id: mainTabBar + anchors.top: pageHeader.bottom + anchors.left: parent.left + anchors.right: parent.right + width: parent.width + + background: Rectangle { + color: AmneziaStyle.color.transparent + Rectangle { + width: parent.width + height: 1 + anchors.bottom: parent.bottom + color: AmneziaStyle.color.slateGray } } - TabBar { - id: mainTabBar - Layout.fillWidth: true - Layout.topMargin: 4 - - background: Rectangle { - color: AmneziaStyle.color.transparent - Rectangle { - width: parent.width - height: 1 - anchors.bottom: parent.bottom - color: AmneziaStyle.color.slateGray - } - } - - TabButtonType { - text: qsTr("Connection") - isSelected: mainTabBar.currentIndex === 0 - } - TabButtonType { - text: qsTr("Settings") - isSelected: mainTabBar.currentIndex === 1 - } + TabButtonType { + text: qsTr("Connection") + isSelected: mainTabBar.currentIndex === 0 + } + TabButtonType { + text: qsTr("Settings") + isSelected: mainTabBar.currentIndex === 1 } } StackLayout { id: tabContent - anchors.top: pageHeader.bottom + anchors.top: mainTabBar.bottom anchors.bottom: parent.bottom anchors.left: parent.left anchors.right: parent.right @@ -318,36 +440,11 @@ PageType { width: connectionListView.width spacing: 0 - function domainToHex(domain) { - var hex = "" - for (var i = 0; i < domain.length; i++) { - var code = domain.charCodeAt(i).toString(16) - hex += (code.length < 2 ? "0" : "") + code - } - return hex - } - - function secretForMode(mode) { - if (mode === "faketls") { - var domain = root.savedTlsDomain !== "" ? root.savedTlsDomain : TelemtConfigModel.defaultTlsDomain() - return "ee" + secret + domainToHex(domain) - } else if (mode === "padded") { - return "dd" + secret - } - // Telemt default (secure MTProto, not FakeTLS): Telegram proxy links require dd + hex secret - return "dd" + secret - } - property int secretTabIndex: root.syncedSecretTabIndex function activeSecret() { - if (root.syncedSecretTabIndex === 0) { - return secretForMode("standard") - } - if (root.syncedSecretTabIndex === 1) { - return secretForMode("padded") - } - return secretForMode("faketls") + return root.telemtClientSecretForTabIndex(secret, root.syncedSecretTabIndex, + root.savedTlsDomain, TelemtConfigModel.defaultTlsDomain()) } function effectiveSecret() { @@ -690,6 +787,11 @@ PageType { width: settingsListView.width spacing: 0 + function telemtActiveSecretForBaseHex(baseHex) { + return root.telemtClientSecretForTabIndex(baseHex, root.syncedSecretTabIndex, + root.savedTlsDomain, TelemtConfigModel.defaultTlsDomain()) + } + SwitcherType { id: enableTelemtSwitch Layout.fillWidth: true @@ -699,11 +801,13 @@ PageType { Layout.bottomMargin: 16 text: qsTr("Enable Telemt") checked: isEnabled - enabled: !isCheckingStatus && containerStatus !== 0 && containerStatus !== 3 && !isUpdating + enabled: containerStatus !== 0 && containerStatus !== 3 && !root.pageBusy + && !root.telemtNetworkBlocked onToggled: function () { if (checked !== isEnabled) { previousEnabled = isEnabled previousContainerStatus = containerStatus + root.previousSecret = secret isEnabled = checked isUpdating = true if (checked) { @@ -736,13 +840,14 @@ PageType { CaptionTextType { Layout.fillWidth: true - text: secret !== "" ? secret : qsTr("Not generated") + text: secret !== "" ? telemtActiveSecretForBaseHex(secret) : qsTr("Not generated") color: secret !== "" ? AmneziaStyle.color.paleGray : AmneziaStyle.color.mutedGray - elide: Text.ElideMiddle + wrapMode: Text.WrapAnywhere font.pixelSize: 14 } ImageButtonType { + Layout.alignment: Qt.AlignTop implicitWidth: 36 implicitHeight: 36 hoverEnabled: true @@ -750,12 +855,14 @@ PageType { imageColor: AmneziaStyle.color.paleGray visible: ServersUiController.isProcessedServerHasWriteAccess() onClicked: { + var secretSnapshot = secret showQuestionDrawer( qsTr("Generate new secret?"), qsTr("All existing connection links will stop working. Users will need new links."), qsTr("Generate"), qsTr("Cancel"), function () { + root.previousSecret = secretSnapshot if (containerStatus === 1) { isUpdating = true TelemtConfigModel.generateSecret() @@ -926,6 +1033,7 @@ PageType { clickedFunction: function () { transportMode = (index === 0) ? "standard" : "faketls" TelemtConfigModel.setTransportMode(transportMode) + root.syncedSecretTabIndex = transportMode === "faketls" ? 1 : 0 transportModeDropDown.closeTriggered() } } @@ -1406,6 +1514,7 @@ PageType { previousNatEnabled = natEnabled previousNatInternalIp = natInternalIp previousNatExternalIp = natExternalIp + root.previousSecret = secret isUpdating = true root.telemtScheduleUpdate(false) } @@ -1414,34 +1523,5 @@ PageType { } } - Rectangle { - anchors.fill: parent - visible: isCheckingStatus || isUpdating || root.telemtNetworkBlocked - color: AmneziaStyle.color.midnightBlack - opacity: 0.6 - z: 1 - MouseArea { - anchors.fill: parent - } - BusyIndicator { - anchors.centerIn: parent - visible: isCheckingStatus || isUpdating - running: isCheckingStatus || isUpdating - width: 48 - height: 48 - } - CaptionTextType { - anchors.left: parent.left - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 24 - anchors.rightMargin: 24 - visible: root.telemtNetworkBlocked && !isCheckingStatus && !isUpdating - horizontalAlignment: Text.AlignHCenter - text: qsTr("No internet connection. Connect to the internet to change Telemt settings.") - color: AmneziaStyle.color.paleGray - wrapMode: Text.WordWrap - font.pixelSize: 14 - } } }