mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-20 02:00:55 +07:00
2005 lines
89 KiB
QML
2005 lines
89 KiB
QML
import QtQuick
|
|
import QtQuick.Controls
|
|
import QtQuick.Layouts
|
|
|
|
import SortFilterProxyModel 0.2
|
|
|
|
import PageEnum 1.0
|
|
import ContainerProps 1.0
|
|
import ProtocolEnum 1.0
|
|
import Style 1.0
|
|
import MtProxyConfig 1.0
|
|
|
|
import "./"
|
|
import "../Controls2"
|
|
import "../Controls2/TextTypes"
|
|
import "../Config"
|
|
import "../Components"
|
|
|
|
|
|
PageType {
|
|
id: root
|
|
|
|
property int containerStatus: 1
|
|
// Last status-query error code (0 = none). 305 = SshTimeoutError → server unreachable.
|
|
property int statusErrorCode: 0
|
|
property bool isUpdating: false
|
|
property bool isCheckingStatus: false
|
|
property bool isFetchingSecret: false
|
|
property bool previousEnabled: true
|
|
property int previousContainerStatus: 1
|
|
|
|
property string previousPort: ""
|
|
property string previousTag: ""
|
|
property string previousPublicHost: ""
|
|
property string previousTransportMode: MtProxyConfigModel.transportModeStandard()
|
|
property string previousTlsDomain: MtProxyConfigModel.defaultTlsDomain()
|
|
property string previousWorkersMode: MtProxyConfigModel.workersModeAuto()
|
|
property string previousWorkers: MtProxyConfigModel.defaultWorkers()
|
|
property bool previousNatEnabled: false
|
|
property string previousNatInternalIp: ""
|
|
property string previousNatExternalIp: ""
|
|
property string previousSecret: ""
|
|
|
|
property string savedTransportMode: ""
|
|
property string savedTlsDomain: ""
|
|
property string savedPublicHost: ""
|
|
|
|
onSavedTransportModeChanged: {
|
|
if (savedTransportMode === "faketls") {
|
|
root.syncedSecretTabIndex = 1
|
|
} else if (savedTransportMode !== "") {
|
|
root.syncedSecretTabIndex = 0
|
|
}
|
|
}
|
|
|
|
property bool diagLoading: false
|
|
property int syncedSecretTabIndex: 0
|
|
property bool pendingEnableAfterRestart: false
|
|
property bool pendingUpdateAfterEnable: false
|
|
property bool diagPortReachable: false
|
|
property bool diagTelegramReachable: false
|
|
property int diagClientsConnected: -1
|
|
property string diagLastConfigRefresh: ""
|
|
property string diagStatsEndpoint: ""
|
|
|
|
readonly property bool mtProxyNetworkBlocked: !NetworkReachabilityController.hasInternetAccess
|
|
|
|
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()
|
|
}
|
|
|
|
property var mtProxyPersistedAdditionalHex: []
|
|
|
|
function mtProxyRefreshPersistedAdditionalSecrets() {
|
|
var list = MtProxyConfigModel.additionalSecretsList()
|
|
var a = []
|
|
for (var i = 0; i < list.length; ++i) {
|
|
a.push(String(list[i]))
|
|
}
|
|
root.mtProxyPersistedAdditionalHex = a
|
|
}
|
|
|
|
function mtProxyIsPersistedAdditionalHex(hex) {
|
|
var h = String(hex)
|
|
for (var j = 0; j < root.mtProxyPersistedAdditionalHex.length; ++j) {
|
|
if (String(root.mtProxyPersistedAdditionalHex[j]) === h) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
readonly property var natIpv4InputFormat: /^(\d{1,3}\.){0,3}\d{0,3}$/
|
|
|
|
function mtProxyScheduleUpdate(closePage) {
|
|
var cp = closePage === undefined ? false : closePage
|
|
Qt.callLater(function () {
|
|
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.MtProxy, cp)
|
|
})
|
|
}
|
|
|
|
function natIpv4FieldShowInvalidError(text) {
|
|
var t = text ? String(text).replace(/^\s+|\s+$/g, '') : ""
|
|
if (t === "")
|
|
return false
|
|
if (MtProxyConfigModel.isValidOptionalIpv4(t))
|
|
return false
|
|
var parts = t.split('.')
|
|
var j
|
|
for (j = 0; j < parts.length; j++) {
|
|
if (parts[j].length > 3)
|
|
return true
|
|
}
|
|
if (parts.length > 4)
|
|
return true
|
|
if (t.indexOf('.') < 0 && t.length > 3)
|
|
return true
|
|
if (t.endsWith('.'))
|
|
return false
|
|
if (parts.length < 4)
|
|
return false
|
|
for (var i = 0; i < parts.length; i++) {
|
|
if (parts[i] === "")
|
|
return true
|
|
}
|
|
return true
|
|
}
|
|
|
|
function statusText() {
|
|
if (isCheckingStatus) {
|
|
return qsTr("Checking...")
|
|
}
|
|
if (isUpdating) {
|
|
return qsTr("Updating")
|
|
}
|
|
switch (containerStatus) {
|
|
case 0: {
|
|
return qsTr("Not deployed")
|
|
}
|
|
case 1: {
|
|
return qsTr("Running")
|
|
}
|
|
case 2: {
|
|
return qsTr("Stopped")
|
|
}
|
|
case 3: {
|
|
return qsTr("Error")
|
|
}
|
|
default: {
|
|
return qsTr("Unknown")
|
|
}
|
|
}
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
root.savedTransportMode = MtProxyConfigModel.getTransportMode()
|
|
root.savedTlsDomain = MtProxyConfigModel.getTlsDomain()
|
|
root.savedPublicHost = MtProxyConfigModel.getPublicHost()
|
|
|
|
Qt.callLater(function () {
|
|
root.mtProxyRefreshPersistedAdditionalSecrets()
|
|
})
|
|
|
|
Qt.callLater(root.mtProxyOnPageShown)
|
|
}
|
|
|
|
onNavigationBlockedWhileBusyChanged: {
|
|
if (root.visible) {
|
|
PageController.disableControls(navigationBlockedWhileBusy)
|
|
}
|
|
}
|
|
|
|
onVisibleChanged: {
|
|
if (!visible) {
|
|
root.pageOpenHandled = false
|
|
containerStatusRefreshCallPending = false
|
|
isCheckingStatus = false
|
|
isFetchingSecret = false
|
|
busyIndicatorShown = false
|
|
statusErrorCode = 0
|
|
PageController.disableControls(false)
|
|
PageController.showBusyIndicator(false)
|
|
diagLoading = false
|
|
} else {
|
|
root.mtProxyOnPageShown()
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: NetworkReachabilityController
|
|
|
|
function onHasInternetAccessChanged() {
|
|
if (!root.visible) {
|
|
return
|
|
}
|
|
if (NetworkReachabilityController.hasInternetAccess) {
|
|
root.mtProxyScheduleContainerStatusRefresh()
|
|
}
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: InstallController
|
|
|
|
function onServerIsBusy(busy) {
|
|
remoteOperationBusy = busy
|
|
}
|
|
|
|
function onUpdateContainerFinished(message, closePage) {
|
|
if (!root.visible) {
|
|
isUpdating = false
|
|
isCheckingStatus = false
|
|
isFetchingSecret = false
|
|
return
|
|
}
|
|
isUpdating = false
|
|
containerStatus = 1
|
|
root.savedTransportMode = MtProxyConfigModel.getTransportMode()
|
|
root.savedTlsDomain = MtProxyConfigModel.getTlsDomain()
|
|
root.savedPublicHost = MtProxyConfigModel.getPublicHost()
|
|
root.mtProxyRefreshPersistedAdditionalSecrets()
|
|
PageController.showNotificationMessage(message)
|
|
}
|
|
|
|
function onInstallationErrorOccurred() {
|
|
if (!root.visible) {
|
|
isUpdating = false
|
|
isCheckingStatus = false
|
|
isFetchingSecret = false
|
|
return
|
|
}
|
|
isUpdating = false
|
|
isFetchingSecret = false
|
|
containerStatus = previousContainerStatus
|
|
MtProxyConfigModel.setEnabled(previousEnabled)
|
|
MtProxyConfigModel.setPort(previousPort)
|
|
MtProxyConfigModel.setTag(previousTag)
|
|
MtProxyConfigModel.setPublicHost(previousPublicHost)
|
|
MtProxyConfigModel.setTransportMode(previousTransportMode)
|
|
MtProxyConfigModel.setTlsDomain(previousTlsDomain)
|
|
MtProxyConfigModel.setWorkersMode(previousWorkersMode)
|
|
MtProxyConfigModel.setWorkers(previousWorkers)
|
|
MtProxyConfigModel.setNatEnabled(previousNatEnabled)
|
|
MtProxyConfigModel.setNatInternalIp(previousNatInternalIp)
|
|
MtProxyConfigModel.setNatExternalIp(previousNatExternalIp)
|
|
if (previousSecret !== "") {
|
|
MtProxyConfigModel.setSecret(previousSecret)
|
|
}
|
|
}
|
|
|
|
function onSetContainerEnabledFinished(enabled) {
|
|
if (!root.visible) {
|
|
isUpdating = false
|
|
return
|
|
}
|
|
if (enabled && pendingUpdateAfterEnable) {
|
|
pendingUpdateAfterEnable = false
|
|
isUpdating = true
|
|
root.mtProxyScheduleUpdate(false)
|
|
return
|
|
}
|
|
isUpdating = false
|
|
containerStatus = enabled ? 1 : 2
|
|
PageController.showNotificationMessage(
|
|
enabled ? qsTr("MTProxy started") : qsTr("MTProxy stopped"))
|
|
}
|
|
|
|
function onContainerStatusRefreshed(status, errorCode) {
|
|
if (!root.visible) {
|
|
isCheckingStatus = false
|
|
isFetchingSecret = false
|
|
return
|
|
}
|
|
containerStatus = status
|
|
root.statusErrorCode = errorCode
|
|
if (status === 3 && errorCode !== 0) {
|
|
PageController.showNotificationMessage(
|
|
qsTr("Settings locked: connection timed out (error code %1). Re-open the page to retry.").arg(errorCode))
|
|
}
|
|
|
|
root.savedTransportMode = MtProxyConfigModel.getTransportMode()
|
|
root.savedTlsDomain = MtProxyConfigModel.getTlsDomain()
|
|
root.savedPublicHost = MtProxyConfigModel.getPublicHost()
|
|
if (status === 1) {
|
|
MtProxyConfigModel.setEnabled(true)
|
|
isFetchingSecret = true
|
|
isCheckingStatus = false
|
|
InstallController.fetchContainerSecret(ServersUiController.processedServerId, ServersUiController.processedContainerIndex)
|
|
} else {
|
|
isFetchingSecret = false
|
|
isCheckingStatus = false
|
|
if (status === 2) {
|
|
MtProxyConfigModel.setEnabled(false)
|
|
}
|
|
}
|
|
syncPageBusyIndicator()
|
|
}
|
|
|
|
function onContainerDiagnosticsRefreshed(portReachable, upstreamReachable, clientsConnected, lastConfigRefresh, statsEndpoint) {
|
|
if (!root.visible) {
|
|
return
|
|
}
|
|
diagLoading = false
|
|
diagPortReachable = portReachable
|
|
diagTelegramReachable = upstreamReachable
|
|
diagClientsConnected = clientsConnected
|
|
diagLastConfigRefresh = lastConfigRefresh
|
|
diagStatsEndpoint = statsEndpoint
|
|
}
|
|
|
|
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 + PageController.safeAreaTopMargin
|
|
|
|
onFocusChanged: {
|
|
if (this.activeFocus) {
|
|
if (mainTabBar.currentIndex === 0) {
|
|
connectionListView.positionViewAtBeginning()
|
|
} else {
|
|
settingsListView.positionViewAtBeginning()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ColumnLayout {
|
|
id: pageHeader
|
|
anchors.top: backButton.bottom
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
spacing: 0
|
|
|
|
BaseHeaderType {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: 16
|
|
Layout.rightMargin: 16
|
|
Layout.bottomMargin: 24
|
|
|
|
headerText: qsTr("MTProxy settings")
|
|
descriptionLinkText: qsTr("Read more about this settings")
|
|
descriptionLinkUrl: "https://core.telegram.org/proxy"
|
|
}
|
|
|
|
CaptionTextType {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: 16
|
|
Layout.rightMargin: 16
|
|
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
|
|
}
|
|
}
|
|
|
|
TabButtonType {
|
|
text: qsTr("Connection")
|
|
isSelected: mainTabBar.currentIndex === 0
|
|
}
|
|
TabButtonType {
|
|
text: qsTr("Settings")
|
|
isSelected: mainTabBar.currentIndex === 1
|
|
}
|
|
}
|
|
|
|
StackLayout {
|
|
id: tabContent
|
|
anchors.top: mainTabBar.bottom
|
|
anchors.bottom: parent.bottom
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
currentIndex: mainTabBar.currentIndex
|
|
|
|
ListViewType {
|
|
id: connectionListView
|
|
model: MtProxyConfigModel
|
|
|
|
delegate: ColumnLayout {
|
|
width: connectionListView.width
|
|
spacing: 0
|
|
|
|
property int secretTabIndex: root.syncedSecretTabIndex
|
|
|
|
function activeSecret() {
|
|
return root.mtProxyClientSecretForTabIndex(secret, root.syncedSecretTabIndex,
|
|
root.savedTlsDomain, MtProxyConfigModel.defaultTlsDomain())
|
|
}
|
|
|
|
function effectiveSecret() {
|
|
return activeSecret()
|
|
}
|
|
|
|
function effectiveHost() {
|
|
return root.savedPublicHost !== "" ? root.savedPublicHost : ServersUiController.serverHostName(ServersUiController.processedServerId)
|
|
}
|
|
|
|
function tmeLink() {
|
|
return "https://t.me/proxy?server=" + effectiveHost() + "&port=" + port + "&secret=" + activeSecret()
|
|
}
|
|
|
|
function tgLink() {
|
|
return "tg://proxy?server=" + effectiveHost() + "&port=" + port + "&secret=" + activeSecret()
|
|
}
|
|
|
|
CaptionTextType {
|
|
Layout.fillWidth: true
|
|
Layout.topMargin: 24
|
|
Layout.leftMargin: 16
|
|
Layout.rightMargin: 16
|
|
Layout.bottomMargin: 8
|
|
text: qsTr("Use Telegram connection link")
|
|
color: AmneziaStyle.color.mutedGray
|
|
}
|
|
|
|
Rectangle {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: 16
|
|
Layout.rightMargin: 16
|
|
Layout.bottomMargin: 16
|
|
implicitHeight: linkRow.implicitHeight + 16
|
|
color: AmneziaStyle.color.onyxBlack
|
|
radius: 8
|
|
border.color: AmneziaStyle.color.slateGray
|
|
border.width: 1
|
|
|
|
RowLayout {
|
|
id: linkRow
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
anchors.leftMargin: 12
|
|
anchors.rightMargin: 8
|
|
spacing: 4
|
|
|
|
CaptionTextType {
|
|
Layout.fillWidth: true
|
|
text: secret !== "" ? tmeLink() : qsTr("Deploy MTProxy first")
|
|
color: secret !== "" ? AmneziaStyle.color.goldenApricot : AmneziaStyle.color.mutedGray
|
|
elide: Text.ElideRight
|
|
maximumLineCount: 1
|
|
font.pixelSize: 13
|
|
}
|
|
|
|
ImageButtonType {
|
|
implicitWidth: 36
|
|
implicitHeight: 36
|
|
hoverEnabled: true
|
|
image: "qrc:/images/controls/qr-code.svg"
|
|
imageColor: AmneziaStyle.color.paleGray
|
|
visible: secret !== ""
|
|
onClicked: {
|
|
ExportController.generateQrFromString(tmeLink())
|
|
PageController.goToShareConnectionPage(
|
|
qsTr("Telegram connection link"),
|
|
qsTr("MTProxy connection link"),
|
|
"", "", "")
|
|
}
|
|
}
|
|
|
|
ImageButtonType {
|
|
implicitWidth: 36
|
|
implicitHeight: 36
|
|
hoverEnabled: true
|
|
image: "qrc:/images/controls/copy.svg"
|
|
imageColor: AmneziaStyle.color.paleGray
|
|
visible: secret !== ""
|
|
onClicked: {
|
|
GC.copyToClipBoard(tmeLink())
|
|
PageController.showNotificationMessage(qsTr("Copied"))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: 16
|
|
Layout.rightMargin: 16
|
|
Layout.bottomMargin: 16
|
|
implicitHeight: tgLinkRow.implicitHeight + 16
|
|
color: AmneziaStyle.color.onyxBlack
|
|
radius: 8
|
|
border.color: AmneziaStyle.color.slateGray
|
|
border.width: 1
|
|
visible: secret !== ""
|
|
|
|
RowLayout {
|
|
id: tgLinkRow
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
anchors.leftMargin: 12
|
|
anchors.rightMargin: 8
|
|
spacing: 4
|
|
|
|
CaptionTextType {
|
|
Layout.fillWidth: true
|
|
text: tgLink()
|
|
color: AmneziaStyle.color.goldenApricot
|
|
elide: Text.ElideRight
|
|
maximumLineCount: 1
|
|
font.pixelSize: 13
|
|
}
|
|
|
|
ImageButtonType {
|
|
implicitWidth: 36
|
|
implicitHeight: 36
|
|
hoverEnabled: true
|
|
image: "qrc:/images/controls/qr-code.svg"
|
|
imageColor: AmneziaStyle.color.paleGray
|
|
onClicked: {
|
|
ExportController.generateQrFromString(tgLink())
|
|
PageController.goToShareConnectionPage(
|
|
qsTr("Telegram connection link"),
|
|
qsTr("MTProxy connection link"),
|
|
"", "", "")
|
|
}
|
|
}
|
|
|
|
ImageButtonType {
|
|
implicitWidth: 36
|
|
implicitHeight: 36
|
|
hoverEnabled: true
|
|
image: "qrc:/images/controls/copy.svg"
|
|
imageColor: AmneziaStyle.color.paleGray
|
|
onClicked: {
|
|
GC.copyToClipBoard(tgLink())
|
|
PageController.showNotificationMessage(qsTr("Copied"))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: 16
|
|
Layout.rightMargin: 16
|
|
Layout.bottomMargin: 8
|
|
spacing: 4
|
|
|
|
CaptionTextType {
|
|
text: qsTr("Or enter the proxy details manually.")
|
|
color: AmneziaStyle.color.mutedGray
|
|
}
|
|
|
|
CaptionTextType {
|
|
Layout.fillWidth: true
|
|
text: qsTr("How to do it")
|
|
color: AmneziaStyle.color.goldenApricot
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: Qt.openUrlExternally("https://core.telegram.org/proxy")
|
|
}
|
|
}
|
|
|
|
Item {
|
|
Layout.fillWidth: true
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: 16
|
|
Layout.rightMargin: 16
|
|
Layout.bottomMargin: 32
|
|
implicitHeight: manualCol.implicitHeight + 8
|
|
color: AmneziaStyle.color.onyxBlack
|
|
radius: 8
|
|
border.color: AmneziaStyle.color.slateGray
|
|
border.width: 1
|
|
|
|
ColumnLayout {
|
|
id: manualCol
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
anchors.top: parent.top
|
|
anchors.topMargin: 8
|
|
spacing: 0
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: 12
|
|
Layout.rightMargin: 8
|
|
Layout.bottomMargin: 8
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
spacing: 2
|
|
CaptionTextType {
|
|
text: qsTr("Host")
|
|
color: AmneziaStyle.color.mutedGray
|
|
font.pixelSize: 12
|
|
}
|
|
CaptionTextType {
|
|
Layout.fillWidth: true
|
|
text: effectiveHost()
|
|
color: AmneziaStyle.color.paleGray
|
|
elide: Text.ElideRight
|
|
}
|
|
}
|
|
ImageButtonType {
|
|
implicitWidth: 36
|
|
implicitHeight: 36
|
|
hoverEnabled: true
|
|
image: "qrc:/images/controls/copy.svg"
|
|
imageColor: AmneziaStyle.color.paleGray
|
|
onClicked: { GC.copyToClipBoard(effectiveHost())
|
|
PageController.showNotificationMessage(qsTr("Copied")) }
|
|
}
|
|
}
|
|
|
|
DividerType {
|
|
Layout.fillWidth: true
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: 12
|
|
Layout.rightMargin: 8
|
|
Layout.topMargin: 8
|
|
Layout.bottomMargin: 8
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
spacing: 2
|
|
CaptionTextType {
|
|
text: qsTr("Port")
|
|
color: AmneziaStyle.color.mutedGray
|
|
font.pixelSize: 12
|
|
}
|
|
CaptionTextType {
|
|
Layout.fillWidth: true
|
|
text: port
|
|
color: AmneziaStyle.color.paleGray
|
|
}
|
|
}
|
|
ImageButtonType {
|
|
implicitWidth: 36
|
|
implicitHeight: 36
|
|
hoverEnabled: true
|
|
image: "qrc:/images/controls/copy.svg"
|
|
imageColor: AmneziaStyle.color.paleGray
|
|
onClicked: { GC.copyToClipBoard(port)
|
|
PageController.showNotificationMessage(qsTr("Copied")) }
|
|
}
|
|
}
|
|
|
|
DividerType {
|
|
Layout.fillWidth: true
|
|
}
|
|
|
|
ButtonGroup {
|
|
id: secretTabGroup
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: 12
|
|
Layout.rightMargin: 8
|
|
Layout.topMargin: 4
|
|
Layout.bottomMargin: 8
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
spacing: 2
|
|
CaptionTextType {
|
|
text: qsTr("Secret")
|
|
color: AmneziaStyle.color.mutedGray
|
|
font.pixelSize: 12
|
|
}
|
|
CaptionTextType {
|
|
Layout.fillWidth: true
|
|
text: activeSecret()
|
|
color: AmneziaStyle.color.paleGray
|
|
wrapMode: Text.WrapAnywhere
|
|
font.pixelSize: 13
|
|
}
|
|
}
|
|
ImageButtonType {
|
|
implicitWidth: 36
|
|
implicitHeight: 36
|
|
hoverEnabled: true
|
|
image: "qrc:/images/controls/copy.svg"
|
|
imageColor: AmneziaStyle.color.paleGray
|
|
onClicked: { GC.copyToClipBoard(activeSecret())
|
|
PageController.showNotificationMessage(qsTr("Copied")) }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
LabelWithButtonType {
|
|
id: removeButton
|
|
Layout.fillWidth: true
|
|
Layout.bottomMargin: 24
|
|
Layout.leftMargin: 0
|
|
Layout.rightMargin: 16
|
|
visible: ServersUiController.isProcessedServerHasWriteAccess()
|
|
text: qsTr("Delete MTProxy")
|
|
textColor: AmneziaStyle.color.vibrantRed
|
|
clickedFunction: function () {
|
|
var headerText = qsTr("Remove %1 from server?").arg(ContainersModel.getProcessedContainerName())
|
|
var descriptionText = qsTr("The proxy will be stopped and all users will lose access.")
|
|
var yesButtonText = qsTr("Continue")
|
|
var noButtonText = qsTr("Cancel")
|
|
var yesButtonFunction = function () {
|
|
PageController.goToPage(PageEnum.PageDeinstalling)
|
|
InstallController.removeContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex)
|
|
}
|
|
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, function () {
|
|
})
|
|
}
|
|
MouseArea {
|
|
anchors.fill: removeButton
|
|
cursorShape: Qt.PointingHandCursor
|
|
enabled: false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ListViewType {
|
|
id: settingsListView
|
|
model: MtProxyConfigModel
|
|
reuseItems: false
|
|
|
|
delegate: ColumnLayout {
|
|
id: settingsRoot
|
|
width: settingsListView.width
|
|
spacing: 0
|
|
|
|
readonly property bool fieldsEditable: isEnabled && containerStatus === 1 && !root.pageBusy
|
|
|
|
function mtProxyActiveSecretForBaseHex(baseHex) {
|
|
return root.mtProxyClientSecretForTabIndex(baseHex, root.syncedSecretTabIndex,
|
|
root.savedTlsDomain, MtProxyConfigModel.defaultTlsDomain())
|
|
}
|
|
|
|
function mtProxyEffectiveHostForLinks() {
|
|
return root.savedPublicHost !== "" ? root.savedPublicHost : ServersUiController.serverHostName(ServersUiController.processedServerId)
|
|
}
|
|
|
|
function mtProxyTmeLinkForAdditional(baseHex) {
|
|
return "https://t.me/proxy?server=" + mtProxyEffectiveHostForLinks() + "&port=" + port + "&secret=" + mtProxyActiveSecretForBaseHex(baseHex)
|
|
}
|
|
|
|
function mtProxyTgLinkForAdditional(baseHex) {
|
|
return "tg://proxy?server=" + mtProxyEffectiveHostForLinks() + "&port=" + port + "&secret=" + mtProxyActiveSecretForBaseHex(baseHex)
|
|
}
|
|
|
|
SwitcherType {
|
|
id: enableMtProxySwitch
|
|
Layout.fillWidth: true
|
|
Layout.topMargin: 24
|
|
Layout.leftMargin: 16
|
|
Layout.rightMargin: 16
|
|
Layout.bottomMargin: 16
|
|
text: qsTr("Enable MTProxy")
|
|
checked: isEnabled
|
|
enabled: containerStatus !== 0 && containerStatus !== 3 && !root.pageBusy
|
|
&& !root.mtProxyNetworkBlocked
|
|
onToggled: function () {
|
|
if (checked !== isEnabled) {
|
|
previousEnabled = isEnabled
|
|
previousContainerStatus = containerStatus
|
|
root.previousSecret = secret
|
|
isEnabled = checked
|
|
isUpdating = true
|
|
if (checked) {
|
|
root.pendingUpdateAfterEnable = true
|
|
InstallController.setContainerEnabled(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, true)
|
|
} else {
|
|
InstallController.setContainerEnabled(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, false)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CaptionTextType {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: 16
|
|
Layout.rightMargin: 16
|
|
Layout.bottomMargin: 8
|
|
visible: !fieldsEditable && !root.pageBusy
|
|
text: (containerStatus === 1 || containerStatus === 2)
|
|
? qsTr("Enable MTProxy to edit settings")
|
|
: (statusErrorCode !== 0
|
|
? qsTr("Settings locked: connection timed out (error code %1). Re-open the page to retry.").arg(statusErrorCode)
|
|
: qsTr("Cannot reach the server — settings are unavailable"))
|
|
color: AmneziaStyle.color.mutedGray
|
|
wrapMode: Text.WordWrap
|
|
}
|
|
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
Layout.topMargin: 16
|
|
Layout.leftMargin: 16
|
|
Layout.rightMargin: 16
|
|
Layout.bottomMargin: 16 * 2
|
|
spacing: 4
|
|
|
|
CaptionTextType {
|
|
text: qsTr("Base secret")
|
|
color: AmneziaStyle.color.mutedGray
|
|
font.pixelSize: 12
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
spacing: 8
|
|
|
|
CaptionTextType {
|
|
Layout.fillWidth: true
|
|
text: secret !== "" ? mtProxyActiveSecretForBaseHex(secret) : qsTr("Not generated")
|
|
color: secret !== "" ? AmneziaStyle.color.paleGray : AmneziaStyle.color.mutedGray
|
|
wrapMode: Text.WrapAnywhere
|
|
font.pixelSize: 14
|
|
}
|
|
|
|
ImageButtonType {
|
|
Layout.alignment: Qt.AlignTop
|
|
implicitWidth: 36
|
|
implicitHeight: 36
|
|
hoverEnabled: true
|
|
image: "qrc:/images/controls/refresh-cw.svg"
|
|
imageColor: AmneziaStyle.color.paleGray
|
|
visible: ServersUiController.isProcessedServerHasWriteAccess()
|
|
enabled: fieldsEditable
|
|
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
|
|
MtProxyConfigModel.generateSecret()
|
|
root.mtProxyScheduleUpdate(false)
|
|
} else {
|
|
MtProxyConfigModel.generateSecret()
|
|
PageController.showNotificationMessage(qsTr("New secret saved. It will be applied when MTProxy is started."))
|
|
}
|
|
},
|
|
function () {
|
|
}
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TextFieldWithHeaderType {
|
|
id: publicHostTextField
|
|
enabled: fieldsEditable
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: 16
|
|
Layout.rightMargin: 16
|
|
Layout.bottomMargin: 4
|
|
headerText: qsTr("Public host / IP")
|
|
textField.placeholderText: ServersUiController.serverHostName(ServersUiController.processedServerId)
|
|
textField.text: publicHost
|
|
textField.maximumLength: 253
|
|
textField.validator: PublicHostInputValidator {
|
|
}
|
|
textField.onTextChanged: {
|
|
var t = publicHostTextField.textField.text
|
|
if (MtProxyConfigModel.isPublicHostTypingIncomplete(t)) {
|
|
publicHostTextField.errorText = ""
|
|
} else if (!MtProxyConfigModel.isValidPublicHost(t)) {
|
|
publicHostTextField.errorText = qsTr("Enter a valid IP address or domain name")
|
|
} else {
|
|
publicHostTextField.errorText = ""
|
|
}
|
|
}
|
|
textField.onEditingFinished: {
|
|
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
|
|
if (!MtProxyConfigModel.isValidPublicHost(textField.text)) {
|
|
publicHostTextField.errorText = qsTr("Enter a valid IP address or domain name")
|
|
return
|
|
}
|
|
publicHostTextField.errorText = ""
|
|
if (textField.text !== publicHost) {
|
|
publicHost = textField.text
|
|
MtProxyConfigModel.setPublicHost(publicHost)
|
|
}
|
|
}
|
|
}
|
|
|
|
CaptionTextType {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: 16
|
|
Layout.rightMargin: 16
|
|
Layout.bottomMargin: 4
|
|
visible: publicHostTextField.textField.text === ""
|
|
text: qsTr("Leave empty to use server IP automatically")
|
|
color: AmneziaStyle.color.mutedGray
|
|
font.pixelSize: 12
|
|
wrapMode: Text.WordWrap
|
|
}
|
|
|
|
CaptionTextType {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: 16
|
|
Layout.rightMargin: 16
|
|
Layout.bottomMargin: 12
|
|
visible: publicHostTextField.textField.text !== "" &&
|
|
publicHostTextField.textField.text !== ServersUiController.serverHostName(ServersUiController.processedServerId)
|
|
text: qsTr("⚠ This overrides the server IP in connection links. Make sure this host/domain points to your server.")
|
|
color: AmneziaStyle.color.goldenApricot
|
|
font.pixelSize: 12
|
|
wrapMode: Text.WordWrap
|
|
}
|
|
|
|
TextFieldWithHeaderType {
|
|
id: portTextField
|
|
enabled: fieldsEditable
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: 16
|
|
Layout.rightMargin: 16
|
|
Layout.bottomMargin: 16
|
|
headerText: qsTr("Server port")
|
|
textField.placeholderText: MtProxyConfigModel.defaultPort()
|
|
textField.maximumLength: 5
|
|
textField.inputMethodHints: Qt.ImhDigitsOnly
|
|
textField.validator: IntValidator {
|
|
bottom: 1
|
|
top: 65535
|
|
}
|
|
Component.onCompleted: {
|
|
var savedPort = port
|
|
textField.text = (savedPort === MtProxyConfigModel.defaultPort()) ? "" : savedPort
|
|
}
|
|
textField.onTextChanged: {
|
|
var cur = portTextField.textField.text
|
|
var clean = MtProxyConfigModel.sanitizePortFieldText(cur)
|
|
if (clean !== cur) {
|
|
textField.text = clean
|
|
textField.cursorPosition = clean.length
|
|
}
|
|
}
|
|
textField.onEditingFinished: {
|
|
textField.text = MtProxyConfigModel.sanitizePortFieldText(textField.text)
|
|
}
|
|
}
|
|
|
|
CaptionTextType {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: 16
|
|
Layout.rightMargin: 16
|
|
Layout.bottomMargin: 12
|
|
visible: transportMode === "faketls" && portTextField.textField.text !== "443" && portTextField.textField.text !== ""
|
|
text: qsTr("FakeTLS may not work on ports other than 443")
|
|
color: AmneziaStyle.color.goldenApricot
|
|
font.pixelSize: 12
|
|
wrapMode: Text.WordWrap
|
|
}
|
|
|
|
CaptionTextType {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: 16
|
|
Layout.rightMargin: 16
|
|
Layout.bottomMargin: 8
|
|
text: qsTr("The promoted channel is set in @MTProxyBot. Paste the proxy tag here: exactly 32 hexadecimal characters (0-9, A-F), as in the bot message — or leave empty.")
|
|
color: AmneziaStyle.color.mutedGray
|
|
font.pixelSize: 12
|
|
wrapMode: Text.WordWrap
|
|
}
|
|
|
|
TextFieldWithHeaderType {
|
|
id: tagTextField
|
|
enabled: fieldsEditable
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: 16
|
|
Layout.rightMargin: 16
|
|
Layout.bottomMargin: 16
|
|
headerText: qsTr("MTProxy bot tag (optional)")
|
|
textField.placeholderText: qsTr("32 hex chars from @MTProxyBot (e.g. 3b7b2fa9…)")
|
|
textField.text: tag
|
|
textField.maximumLength: MtProxyConfigModel.mtProxyBotTagHexLength()
|
|
textField.onTextChanged: {
|
|
var cur = tagTextField.textField.text
|
|
var clean = MtProxyConfigModel.sanitizeMtProxyTagFieldText(cur)
|
|
if (clean !== cur) {
|
|
textField.text = clean
|
|
textField.cursorPosition = clean.length
|
|
return
|
|
}
|
|
var tt = tagTextField.textField.text
|
|
if (tt === "") {
|
|
tagTextField.errorText = ""
|
|
return
|
|
}
|
|
if (MtProxyConfigModel.isMtProxyTagTypingIncomplete(tt)) {
|
|
tagTextField.errorText = ""
|
|
return
|
|
}
|
|
if (!MtProxyConfigModel.isValidMtProxyTag(tt)) {
|
|
tagTextField.errorText = qsTr("Proxy tag must be exactly 32 hexadecimal characters (0-9, A-F).")
|
|
return
|
|
}
|
|
tagTextField.errorText = ""
|
|
}
|
|
textField.onEditingFinished: {
|
|
var raw = textField.text.replace(/^\s+|\s+$/g, '')
|
|
var normalized = MtProxyConfigModel.sanitizeMtProxyTagFieldText(raw)
|
|
textField.text = normalized
|
|
if (!MtProxyConfigModel.isValidMtProxyTag(normalized)) {
|
|
tagTextField.errorText = qsTr("Proxy tag must be exactly 32 hexadecimal characters (0-9, A-F). Leave empty if unused.")
|
|
return
|
|
}
|
|
tagTextField.errorText = ""
|
|
if (normalized !== tag) {
|
|
tag = normalized
|
|
MtProxyConfigModel.setTag(tag)
|
|
}
|
|
}
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: 16
|
|
Layout.rightMargin: 16
|
|
Layout.bottomMargin: 16
|
|
spacing: 4
|
|
|
|
CaptionTextType {
|
|
text: qsTr("Get a tag from")
|
|
color: AmneziaStyle.color.mutedGray
|
|
font.pixelSize: 12
|
|
}
|
|
CaptionTextType {
|
|
text: "@MTProxyBot"
|
|
color: AmneziaStyle.color.goldenApricot
|
|
font.pixelSize: 12
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: Qt.openUrlExternally("https://t.me/MTProxyBot")
|
|
}
|
|
}
|
|
}
|
|
|
|
CaptionTextType {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: 16
|
|
Layout.rightMargin: 16
|
|
Layout.topMargin: 16 * 2
|
|
text: qsTr("Transport mode")
|
|
color: AmneziaStyle.color.mutedGray
|
|
font.pixelSize: 12
|
|
}
|
|
|
|
DropDownType {
|
|
id: transportModeDropDown
|
|
enabled: fieldsEditable
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: 16
|
|
Layout.rightMargin: 16
|
|
Layout.bottomMargin: 16
|
|
|
|
drawerParent: root
|
|
drawerHeight: 0.4
|
|
headerText: qsTr("Transport mode")
|
|
text: transportMode === "faketls" ? qsTr("FakeTLS") : qsTr("Standard MTProto")
|
|
|
|
listView: Component {
|
|
ListViewType {
|
|
model: [qsTr("Standard MTProto"), qsTr("FakeTLS")]
|
|
delegate: LabelWithButtonType {
|
|
Layout.fillWidth: true
|
|
text: modelData
|
|
rightImageSource: {
|
|
var isCurrent = (index === 0 && transportMode === "standard") ||
|
|
(index === 1 && transportMode === "faketls")
|
|
return isCurrent ? "qrc:/images/controls/check.svg" : ""
|
|
}
|
|
rightImageColor: AmneziaStyle.color.goldenApricot
|
|
clickedFunction: function () {
|
|
transportMode = (index === 0) ? "standard" : "faketls"
|
|
MtProxyConfigModel.setTransportMode(transportMode)
|
|
root.syncedSecretTabIndex = transportMode === "faketls" ? 1 : 0
|
|
transportModeDropDown.closeTriggered()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TextFieldWithHeaderType {
|
|
id: tlsDomainTextField
|
|
enabled: fieldsEditable
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: 16
|
|
Layout.rightMargin: 16
|
|
Layout.bottomMargin: 16
|
|
visible: transportMode === "faketls"
|
|
headerText: qsTr("FakeTLS domain")
|
|
textField.placeholderText: root.previousTlsDomain
|
|
textField.validator: RegularExpressionValidator {
|
|
regularExpression: /^[A-Za-z0-9.-]*$/
|
|
}
|
|
Component.onCompleted: {
|
|
var savedDomain = tlsDomain
|
|
textField.text = (savedDomain === MtProxyConfigModel.defaultTlsDomain() || savedDomain === "") ? "" : savedDomain
|
|
}
|
|
textField.onTextChanged: {
|
|
var t = tlsDomainTextField.textField.text
|
|
if (t === "" || MtProxyConfigModel.isFakeTlsDomainTypingIncomplete(t)
|
|
|| MtProxyConfigModel.isValidFakeTlsDomain(t)) {
|
|
tlsDomainTextField.errorText = ""
|
|
} else {
|
|
tlsDomainTextField.errorText = qsTr("Enter a valid domain name")
|
|
}
|
|
}
|
|
textField.onEditingFinished: {
|
|
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
|
|
var domainValue = textField.text === "" ? MtProxyConfigModel.defaultTlsDomain() : textField.text
|
|
if (!MtProxyConfigModel.isValidFakeTlsDomain(domainValue)) {
|
|
tlsDomainTextField.errorText = qsTr("Enter a valid domain name")
|
|
return
|
|
}
|
|
tlsDomainTextField.errorText = ""
|
|
if (domainValue !== tlsDomain) {
|
|
tlsDomain = domainValue
|
|
MtProxyConfigModel.setTlsDomain(tlsDomain)
|
|
}
|
|
}
|
|
}
|
|
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: 16
|
|
Layout.rightMargin: 16
|
|
Layout.bottomMargin: 16
|
|
spacing: 4
|
|
visible: transportMode === "faketls"
|
|
|
|
CaptionTextType {
|
|
Layout.fillWidth: true
|
|
text: qsTr("The domain is encoded into the FakeTLS client secret (ee + base_secret + hex(domain)). It must support HTTPS / TLS 1.3.")
|
|
color: AmneziaStyle.color.mutedGray
|
|
wrapMode: Text.WordWrap
|
|
font.pixelSize: 12
|
|
}
|
|
CaptionTextType {
|
|
Layout.fillWidth: true
|
|
text: qsTr("\u26a0 Changing the domain will invalidate all previously issued FakeTLS connection links.")
|
|
color: AmneziaStyle.color.goldenApricot
|
|
wrapMode: Text.WordWrap
|
|
font.pixelSize: 12
|
|
}
|
|
}
|
|
|
|
LabelWithButtonType {
|
|
id: advancedHeader
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: 0
|
|
Layout.rightMargin: 16
|
|
property bool expanded: false
|
|
text: qsTr("Advanced")
|
|
rightImageSource: expanded
|
|
? "qrc:/images/controls/chevron-up.svg"
|
|
: "qrc:/images/controls/chevron-down.svg"
|
|
rightImageColor: AmneziaStyle.color.mutedGray
|
|
clickedFunction: function () {
|
|
expanded = !expanded
|
|
}
|
|
}
|
|
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
spacing: 0
|
|
visible: advancedHeader.expanded
|
|
enabled: fieldsEditable
|
|
|
|
CaptionTextType {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: 16
|
|
Layout.rightMargin: 16
|
|
Layout.topMargin: 8
|
|
Layout.bottomMargin: 4
|
|
text: qsTr("Additional secrets")
|
|
color: AmneziaStyle.color.mutedGray
|
|
}
|
|
CaptionTextType {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: 16
|
|
Layout.rightMargin: 16
|
|
Layout.bottomMargin: 8
|
|
text: qsTr("Add extra secrets to allow gradual migration without disconnecting existing users.")
|
|
color: AmneziaStyle.color.charcoalGray
|
|
wrapMode: Text.WordWrap
|
|
font.pixelSize: 12
|
|
}
|
|
|
|
Repeater {
|
|
model: additionalSecrets
|
|
delegate: ColumnLayout {
|
|
id: addSecretDelegate
|
|
property bool linksExpanded: false
|
|
readonly property bool linksPanelAllowed: root.mtProxyIsPersistedAdditionalHex(modelData)
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: 16
|
|
Layout.rightMargin: 16
|
|
Layout.bottomMargin: 8
|
|
spacing: 0
|
|
|
|
onLinksPanelAllowedChanged: {
|
|
if (!linksPanelAllowed) {
|
|
linksExpanded = false
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
Layout.fillWidth: true
|
|
implicitHeight: collapsedBar.implicitHeight + 16
|
|
color: AmneziaStyle.color.onyxBlack
|
|
radius: 8
|
|
border.color: AmneziaStyle.color.slateGray
|
|
border.width: 1
|
|
|
|
RowLayout {
|
|
id: collapsedBar
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
anchors.leftMargin: 12
|
|
anchors.rightMargin: 8
|
|
spacing: 8
|
|
|
|
Item {
|
|
Layout.fillWidth: true
|
|
implicitHeight: Math.max(hexCaption.implicitHeight, 24)
|
|
|
|
RowLayout {
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
spacing: 8
|
|
|
|
CaptionTextType {
|
|
id: hexCaption
|
|
Layout.fillWidth: true
|
|
text: modelData
|
|
color: AmneziaStyle.color.paleGray
|
|
elide: Text.ElideMiddle
|
|
font.pixelSize: 13
|
|
}
|
|
|
|
Image {
|
|
width: 24
|
|
height: 24
|
|
visible: addSecretDelegate.linksPanelAllowed
|
|
source: "qrc:/images/controls/chevron-down.svg"
|
|
sourceSize.width: 24
|
|
sourceSize.height: 24
|
|
rotation: addSecretDelegate.linksExpanded ? 180 : 0
|
|
Behavior on rotation {
|
|
NumberAnimation {
|
|
duration: 150
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
visible: addSecretDelegate.linksPanelAllowed
|
|
enabled: addSecretDelegate.linksPanelAllowed
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: addSecretDelegate.linksExpanded = !addSecretDelegate.linksExpanded
|
|
}
|
|
}
|
|
|
|
ImageButtonType {
|
|
implicitWidth: 32
|
|
implicitHeight: 32
|
|
hoverEnabled: true
|
|
visible: ServersUiController.isProcessedServerHasWriteAccess()
|
|
image: "qrc:/images/controls/trash.svg"
|
|
imageColor: AmneziaStyle.color.vibrantRed
|
|
onClicked: {
|
|
MtProxyConfigModel.removeAdditionalSecret(index)
|
|
if (containerStatus === 1) {
|
|
root.mtProxyScheduleUpdate(false)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
Layout.topMargin: 8
|
|
spacing: 8
|
|
visible: addSecretDelegate.linksPanelAllowed && addSecretDelegate.linksExpanded
|
|
|
|
CaptionTextType {
|
|
Layout.fillWidth: true
|
|
text: qsTr("Use Telegram connection link")
|
|
color: AmneziaStyle.color.mutedGray
|
|
}
|
|
|
|
Rectangle {
|
|
Layout.fillWidth: true
|
|
implicitHeight: expTmeRow.implicitHeight + 16
|
|
color: AmneziaStyle.color.onyxBlack
|
|
radius: 8
|
|
border.color: AmneziaStyle.color.slateGray
|
|
border.width: 1
|
|
|
|
RowLayout {
|
|
id: expTmeRow
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
anchors.leftMargin: 12
|
|
anchors.rightMargin: 8
|
|
spacing: 4
|
|
|
|
CaptionTextType {
|
|
Layout.fillWidth: true
|
|
text: settingsRoot.mtProxyTmeLinkForAdditional(modelData)
|
|
color: AmneziaStyle.color.goldenApricot
|
|
elide: Text.ElideRight
|
|
maximumLineCount: 1
|
|
font.pixelSize: 13
|
|
}
|
|
|
|
ImageButtonType {
|
|
implicitWidth: 36
|
|
implicitHeight: 36
|
|
hoverEnabled: true
|
|
image: "qrc:/images/controls/qr-code.svg"
|
|
imageColor: AmneziaStyle.color.paleGray
|
|
onClicked: {
|
|
ExportController.generateQrFromString(settingsRoot.mtProxyTmeLinkForAdditional(modelData))
|
|
PageController.goToShareConnectionPage(
|
|
qsTr("Telegram connection link"),
|
|
qsTr("MTProxy connection link"),
|
|
"", "", "")
|
|
}
|
|
}
|
|
|
|
ImageButtonType {
|
|
implicitWidth: 36
|
|
implicitHeight: 36
|
|
hoverEnabled: true
|
|
image: "qrc:/images/controls/copy.svg"
|
|
imageColor: AmneziaStyle.color.paleGray
|
|
onClicked: {
|
|
GC.copyToClipBoard(settingsRoot.mtProxyTmeLinkForAdditional(modelData))
|
|
PageController.showNotificationMessage(qsTr("Copied"))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
Layout.fillWidth: true
|
|
implicitHeight: expTgRow.implicitHeight + 16
|
|
color: AmneziaStyle.color.onyxBlack
|
|
radius: 8
|
|
border.color: AmneziaStyle.color.slateGray
|
|
border.width: 1
|
|
|
|
RowLayout {
|
|
id: expTgRow
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
anchors.leftMargin: 12
|
|
anchors.rightMargin: 8
|
|
spacing: 4
|
|
|
|
CaptionTextType {
|
|
Layout.fillWidth: true
|
|
text: settingsRoot.mtProxyTgLinkForAdditional(modelData)
|
|
color: AmneziaStyle.color.goldenApricot
|
|
elide: Text.ElideRight
|
|
maximumLineCount: 1
|
|
font.pixelSize: 13
|
|
}
|
|
|
|
ImageButtonType {
|
|
implicitWidth: 36
|
|
implicitHeight: 36
|
|
hoverEnabled: true
|
|
image: "qrc:/images/controls/qr-code.svg"
|
|
imageColor: AmneziaStyle.color.paleGray
|
|
onClicked: {
|
|
ExportController.generateQrFromString(settingsRoot.mtProxyTgLinkForAdditional(modelData))
|
|
PageController.goToShareConnectionPage(
|
|
qsTr("Telegram connection link"),
|
|
qsTr("MTProxy connection link"),
|
|
"", "", "")
|
|
}
|
|
}
|
|
|
|
ImageButtonType {
|
|
implicitWidth: 36
|
|
implicitHeight: 36
|
|
hoverEnabled: true
|
|
image: "qrc:/images/controls/copy.svg"
|
|
imageColor: AmneziaStyle.color.paleGray
|
|
onClicked: {
|
|
GC.copyToClipBoard(settingsRoot.mtProxyTgLinkForAdditional(modelData))
|
|
PageController.showNotificationMessage(qsTr("Copied"))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
BasicButtonType {
|
|
Layout.fillWidth: true
|
|
Layout.topMargin: 8
|
|
|
|
Layout.leftMargin: 16
|
|
Layout.rightMargin: 16
|
|
Layout.bottomMargin: 16
|
|
text: qsTr("Add additional secret")
|
|
clickedFunc: function () {
|
|
MtProxyConfigModel.addAdditionalSecret()
|
|
}
|
|
}
|
|
|
|
DividerType {
|
|
Layout.fillWidth: true
|
|
Layout.bottomMargin: 8
|
|
}
|
|
|
|
LabelTextType {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: 16
|
|
Layout.bottomMargin: 4
|
|
text: qsTr("Worker mode")
|
|
}
|
|
|
|
ButtonGroup {
|
|
id: workerModeGroup
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: 16
|
|
Layout.rightMargin: 16
|
|
Layout.bottomMargin: 4
|
|
spacing: 0
|
|
visible: transportMode !== "faketls"
|
|
|
|
HorizontalRadioButton {
|
|
Layout.fillWidth: true
|
|
text: qsTr("Auto")
|
|
ButtonGroup.group: workerModeGroup
|
|
checked: workersMode === "auto"
|
|
onClicked: { workersMode = "auto"; MtProxyConfigModel.setWorkersMode("auto") }
|
|
}
|
|
HorizontalRadioButton {
|
|
Layout.fillWidth: true
|
|
text: qsTr("Manual")
|
|
ButtonGroup.group: workerModeGroup
|
|
checked: workersMode === "manual"
|
|
onClicked: { workersMode = "manual"; MtProxyConfigModel.setWorkersMode("manual") }
|
|
}
|
|
}
|
|
|
|
CaptionTextType {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: 16
|
|
Layout.rightMargin: 16
|
|
Layout.bottomMargin: 8
|
|
visible: transportMode === "faketls"
|
|
text: qsTr("Workers are set to 0 automatically for FakeTLS mode.")
|
|
color: AmneziaStyle.color.mutedGray
|
|
font.pixelSize: 12
|
|
wrapMode: Text.WordWrap
|
|
}
|
|
|
|
TextFieldWithHeaderType {
|
|
id: workersTextField
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: 16
|
|
Layout.rightMargin: 16
|
|
Layout.bottomMargin: 16
|
|
visible: workersMode === "manual" && transportMode !== "faketls"
|
|
headerText: qsTr("Workers count")
|
|
textField.placeholderText: "2"
|
|
textField.text: workers
|
|
textField.maximumLength: 2
|
|
textField.inputMethodHints: Qt.ImhDigitsOnly
|
|
// Range input like the port field: IntValidator bounds the value and the
|
|
// clamp keeps it within 0..maxWorkers on every change (rejects 33+, neg.).
|
|
textField.validator: IntValidator {
|
|
bottom: 0
|
|
top: MtProxyConfigModel.maxWorkers()
|
|
}
|
|
textField.onTextChanged: {
|
|
var cur = workersTextField.textField.text
|
|
if (cur === "") {
|
|
return
|
|
}
|
|
var n = parseInt(cur, 10)
|
|
var maxW = MtProxyConfigModel.maxWorkers()
|
|
if (isNaN(n) || n < 0) { n = 0 }
|
|
if (n > maxW) { n = maxW }
|
|
var clamped = String(n)
|
|
if (clamped !== cur) {
|
|
textField.text = clamped
|
|
textField.cursorPosition = clamped.length
|
|
}
|
|
}
|
|
textField.onEditingFinished: {
|
|
var v = workersTextField.textField.text
|
|
if (v !== "") {
|
|
var m = parseInt(v, 10)
|
|
var maxW2 = MtProxyConfigModel.maxWorkers()
|
|
if (isNaN(m) || m < 0) { m = 0 }
|
|
if (m > maxW2) { m = maxW2 }
|
|
v = String(m)
|
|
textField.text = v
|
|
}
|
|
if (v !== workers) {
|
|
workers = v
|
|
MtProxyConfigModel.setWorkers(workers)
|
|
}
|
|
}
|
|
}
|
|
|
|
DividerType {
|
|
Layout.fillWidth: true
|
|
Layout.bottomMargin: 8
|
|
}
|
|
|
|
SwitcherType {
|
|
Layout.fillWidth: true
|
|
Layout.rightMargin: 16
|
|
Layout.leftMargin: 16
|
|
Layout.bottomMargin: 4
|
|
text: qsTr("Server is behind NAT / Docker bridge")
|
|
descriptionText: qsTr("Enable if your server is not directly accessible from the internet, e.g. Docker or private network")
|
|
checked: natEnabled
|
|
onToggled: function () {
|
|
if (checked !== natEnabled) {
|
|
natEnabled = checked
|
|
MtProxyConfigModel.setNatEnabled(natEnabled)
|
|
}
|
|
}
|
|
}
|
|
|
|
TextFieldWithHeaderType {
|
|
id: natInternalIpTextField
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: 16
|
|
Layout.rightMargin: 16
|
|
Layout.bottomMargin: 16
|
|
visible: natEnabled
|
|
headerText: qsTr("Internal IP")
|
|
textField.placeholderText: "172.17.0.2"
|
|
textField.text: natInternalIp
|
|
textField.maximumLength: 15
|
|
textField.validator: RegularExpressionValidator {
|
|
regularExpression: root.natIpv4InputFormat
|
|
}
|
|
textField.onTextChanged: {
|
|
if (root.natIpv4FieldShowInvalidError(textField.text)) {
|
|
natInternalIpTextField.errorText = qsTr("Enter a valid IPv4 address")
|
|
} else {
|
|
natInternalIpTextField.errorText = ""
|
|
}
|
|
}
|
|
textField.onEditingFinished: {
|
|
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
|
|
if (!MtProxyConfigModel.isValidOptionalIpv4(textField.text)) {
|
|
natInternalIpTextField.errorText = qsTr("Enter a valid IPv4 address")
|
|
return
|
|
}
|
|
natInternalIpTextField.errorText = ""
|
|
if (textField.text !== natInternalIp) {
|
|
natInternalIp = textField.text
|
|
MtProxyConfigModel.setNatInternalIp(natInternalIp)
|
|
}
|
|
}
|
|
}
|
|
|
|
TextFieldWithHeaderType {
|
|
id: natExternalIpTextField
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: 16
|
|
Layout.rightMargin: 16
|
|
Layout.bottomMargin: 16
|
|
visible: natEnabled
|
|
headerText: qsTr("External IP")
|
|
textField.placeholderText: "1.2.3.4"
|
|
textField.text: natExternalIp
|
|
textField.maximumLength: 15
|
|
textField.validator: RegularExpressionValidator {
|
|
regularExpression: root.natIpv4InputFormat
|
|
}
|
|
textField.onTextChanged: {
|
|
if (root.natIpv4FieldShowInvalidError(textField.text)) {
|
|
natExternalIpTextField.errorText = qsTr("Enter a valid IPv4 address")
|
|
} else {
|
|
natExternalIpTextField.errorText = ""
|
|
}
|
|
}
|
|
textField.onEditingFinished: {
|
|
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
|
|
if (!MtProxyConfigModel.isValidOptionalIpv4(textField.text)) {
|
|
natExternalIpTextField.errorText = qsTr("Enter a valid IPv4 address")
|
|
return
|
|
}
|
|
natExternalIpTextField.errorText = ""
|
|
if (textField.text !== natExternalIp) {
|
|
natExternalIp = textField.text
|
|
MtProxyConfigModel.setNatExternalIp(natExternalIp)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
DividerType {
|
|
Layout.fillWidth: true
|
|
Layout.topMargin: 8
|
|
}
|
|
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
Layout.topMargin: 16
|
|
Layout.leftMargin: 16
|
|
Layout.rightMargin: 16
|
|
Layout.bottomMargin: 8
|
|
spacing: 8
|
|
visible: containerStatus === 1
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
|
|
Header2Type {
|
|
Layout.fillWidth: true
|
|
headerText: qsTr("Diagnostics")
|
|
}
|
|
|
|
ImageButtonType {
|
|
implicitWidth: 32
|
|
implicitHeight: 32
|
|
image: "qrc:/images/controls/refresh-cw.svg"
|
|
imageColor: diagLoading ? AmneziaStyle.color.mutedGray : AmneziaStyle.color.paleGray
|
|
hoverEnabled: !diagLoading
|
|
enabled: !diagLoading
|
|
onClicked: {
|
|
diagLoading = true
|
|
InstallController.refreshContainerDiagnostics(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, parseInt(port))
|
|
}
|
|
}
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
spacing: 8
|
|
Rectangle {
|
|
width: 8
|
|
height: 8
|
|
radius: 4
|
|
color: diagClientsConnected >= 0 ? (diagPortReachable ? AmneziaStyle.color.paleGray : AmneziaStyle.color.vibrantRed) : AmneziaStyle.color.mutedGray
|
|
}
|
|
CaptionTextType {
|
|
Layout.fillWidth: true
|
|
text: qsTr("Public port reachable")
|
|
color: AmneziaStyle.color.paleGray
|
|
}
|
|
CaptionTextType {
|
|
text: diagClientsConnected < 0 ? qsTr("—") : (diagPortReachable ? qsTr("Yes") : qsTr("No"))
|
|
color: diagClientsConnected >= 0 ? (diagPortReachable ? AmneziaStyle.color.paleGray : AmneziaStyle.color.vibrantRed) : AmneziaStyle.color.mutedGray
|
|
}
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
spacing: 8
|
|
Rectangle {
|
|
width: 8
|
|
height: 8
|
|
radius: 4
|
|
color: diagClientsConnected >= 0 ? (diagTelegramReachable ? AmneziaStyle.color.paleGray : AmneziaStyle.color.vibrantRed) : AmneziaStyle.color.mutedGray
|
|
}
|
|
CaptionTextType {
|
|
Layout.fillWidth: true
|
|
text: qsTr("Telegram upstream reachable")
|
|
color: AmneziaStyle.color.paleGray
|
|
}
|
|
CaptionTextType {
|
|
text: diagClientsConnected < 0 ? qsTr("—") : (diagTelegramReachable ? qsTr("Yes") : qsTr("No"))
|
|
color: diagClientsConnected >= 0 ? (diagTelegramReachable ? AmneziaStyle.color.paleGray : AmneziaStyle.color.vibrantRed) : AmneziaStyle.color.mutedGray
|
|
}
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
spacing: 8
|
|
Rectangle {
|
|
width: 8
|
|
height: 8
|
|
radius: 4
|
|
color: diagClientsConnected >= 0 ? AmneziaStyle.color.goldenApricot : AmneziaStyle.color.mutedGray
|
|
}
|
|
CaptionTextType {
|
|
Layout.fillWidth: true
|
|
text: qsTr("Clients connected")
|
|
color: AmneziaStyle.color.paleGray
|
|
}
|
|
CaptionTextType {
|
|
text: diagClientsConnected < 0 ? qsTr("—") : diagClientsConnected.toString()
|
|
color: AmneziaStyle.color.paleGray
|
|
}
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
spacing: 8
|
|
Rectangle {
|
|
width: 8
|
|
height: 8
|
|
radius: 4
|
|
color: AmneziaStyle.color.mutedGray
|
|
}
|
|
CaptionTextType {
|
|
Layout.fillWidth: true
|
|
text: qsTr("Last config refresh")
|
|
color: AmneziaStyle.color.paleGray
|
|
}
|
|
CaptionTextType {
|
|
text: diagLastConfigRefresh !== "" ? diagLastConfigRefresh : qsTr("—")
|
|
color: AmneziaStyle.color.mutedGray
|
|
}
|
|
}
|
|
|
|
LabelWithButtonType {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: -16
|
|
visible: diagStatsEndpoint !== ""
|
|
text: qsTr("Stats endpoint")
|
|
descriptionText: diagStatsEndpoint
|
|
descriptionOnTop: true
|
|
rightImageSource: "qrc:/images/controls/copy.svg"
|
|
rightImageColor: AmneziaStyle.color.paleGray
|
|
clickedFunction: function () {
|
|
GC.copyToClipBoard(diagStatsEndpoint)
|
|
PageController.showNotificationMessage(qsTr("Copied"))
|
|
}
|
|
}
|
|
|
|
CaptionTextType {
|
|
Layout.fillWidth: true
|
|
text: diagLoading ? qsTr("Refreshing…") : qsTr("Tap ↻ to refresh diagnostics")
|
|
color: AmneziaStyle.color.mutedGray
|
|
visible: diagClientsConnected < 0
|
|
}
|
|
}
|
|
|
|
CaptionTextType {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: 16
|
|
Layout.rightMargin: 16
|
|
Layout.topMargin: 16 * 2
|
|
Layout.bottomMargin: 24
|
|
text: qsTr("If you change the settings, the proxy connection link will change. The old link will stop working.")
|
|
color: AmneziaStyle.color.mutedGray
|
|
wrapMode: Text.WordWrap
|
|
font.pixelSize: 12
|
|
}
|
|
|
|
BasicButtonType {
|
|
Layout.fillWidth: true
|
|
Layout.topMargin: 16
|
|
Layout.bottomMargin: 32
|
|
Layout.rightMargin: 16
|
|
Layout.leftMargin: 16
|
|
visible: ServersUiController.isProcessedServerHasWriteAccess()
|
|
enabled: fieldsEditable && !root.mtProxyNetworkBlocked
|
|
text: qsTr("Save")
|
|
clickedFunc: function () {
|
|
if (root.mtProxyNetworkBlocked) {
|
|
PageController.showErrorMessage(qsTr("No internet connection. Connect to the internet to change MTProxy settings."))
|
|
return
|
|
}
|
|
publicHostTextField.errorText = ""
|
|
tagTextField.errorText = ""
|
|
tlsDomainTextField.errorText = ""
|
|
natInternalIpTextField.errorText = ""
|
|
natExternalIpTextField.errorText = ""
|
|
portTextField.errorText = ""
|
|
|
|
var portValue = portTextField.textField.text === ""
|
|
? MtProxyConfigModel.defaultPort()
|
|
: portTextField.textField.text
|
|
|
|
var errorLines = []
|
|
var bullet = "- "
|
|
if (!portTextField.textField.acceptableInput && portTextField.textField.text !== "") {
|
|
var portErr = qsTr("The port must be in the range of 1 to 65535")
|
|
portTextField.errorText = portErr
|
|
errorLines.push(bullet + portErr)
|
|
}
|
|
if (!MtProxyConfigModel.isValidPublicHost(publicHostTextField.textField.text)) {
|
|
var hostErr = qsTr("Enter a valid IP address or domain name")
|
|
publicHostTextField.errorText = hostErr
|
|
errorLines.push(bullet + hostErr)
|
|
}
|
|
var tagNormalized = MtProxyConfigModel.sanitizeMtProxyTagFieldText(tagTextField.textField.text)
|
|
tagTextField.textField.text = tagNormalized
|
|
if (!MtProxyConfigModel.isValidMtProxyTag(tagNormalized)) {
|
|
var tagErr = qsTr("Proxy tag must be exactly 32 hexadecimal characters (0-9, A-F), or leave empty.")
|
|
tagTextField.errorText = tagErr
|
|
errorLines.push(bullet + tagErr)
|
|
}
|
|
var domainValueForSave = tlsDomainTextField.textField.text === ""
|
|
? MtProxyConfigModel.defaultTlsDomain()
|
|
: tlsDomainTextField.textField.text
|
|
if (!MtProxyConfigModel.isValidFakeTlsDomain(domainValueForSave)) {
|
|
var tlsErr = qsTr("Enter a valid domain name")
|
|
tlsDomainTextField.errorText = tlsErr
|
|
errorLines.push(bullet + tlsErr)
|
|
}
|
|
var natIpErr = qsTr("Enter a valid IPv4 address")
|
|
if (!MtProxyConfigModel.isValidOptionalIpv4(natInternalIpTextField.textField.text)) {
|
|
natInternalIpTextField.errorText = natIpErr
|
|
errorLines.push(bullet + qsTr("NAT internal IP: enter a valid IPv4 address"))
|
|
}
|
|
if (!MtProxyConfigModel.isValidOptionalIpv4(natExternalIpTextField.textField.text)) {
|
|
natExternalIpTextField.errorText = natIpErr
|
|
errorLines.push(bullet + qsTr("NAT external IP: enter a valid IPv4 address"))
|
|
}
|
|
if (errorLines.length > 0) {
|
|
PageController.showErrorMessage(errorLines.join("\n"))
|
|
return
|
|
}
|
|
MtProxyConfigModel.setPort(portValue)
|
|
MtProxyConfigModel.setTag(tagNormalized)
|
|
MtProxyConfigModel.setPublicHost(publicHostTextField.textField.text)
|
|
MtProxyConfigModel.setTransportMode(transportMode)
|
|
var domainValue = tlsDomainTextField.textField.text === ""
|
|
? MtProxyConfigModel.defaultTlsDomain()
|
|
: tlsDomainTextField.textField.text
|
|
MtProxyConfigModel.setTlsDomain(domainValue)
|
|
|
|
if (transportMode === "faketls") {
|
|
workers = "0"
|
|
MtProxyConfigModel.setWorkers("0")
|
|
} else {
|
|
MtProxyConfigModel.setWorkersMode(workersMode)
|
|
MtProxyConfigModel.setWorkers(workers)
|
|
}
|
|
MtProxyConfigModel.setNatEnabled(natEnabled)
|
|
MtProxyConfigModel.setNatInternalIp(natInternalIpTextField.textField.text)
|
|
MtProxyConfigModel.setNatExternalIp(natExternalIpTextField.textField.text)
|
|
|
|
previousPort = port
|
|
previousTag = tag
|
|
previousPublicHost = publicHost
|
|
previousTransportMode = transportMode
|
|
previousTlsDomain = tlsDomain
|
|
previousWorkersMode = workersMode
|
|
previousWorkers = workers
|
|
previousNatEnabled = natEnabled
|
|
previousNatInternalIp = natInternalIp
|
|
previousNatExternalIp = natExternalIp
|
|
root.previousSecret = secret
|
|
isUpdating = true
|
|
root.mtProxyScheduleUpdate(false)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|