2026-05-18 14:52:58 +03:00
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
property bool isUpdating: false
property bool isCheckingStatus: false
2026-05-28 06:46:26 +03:00
property bool isFetchingSecret: false
2026-05-18 14:52:58 +03:00
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" ) {
2026-05-28 06:46:26 +03:00
root . syncedSecretTabIndex = 1
2026-05-18 14:52:58 +03:00
} 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
2026-05-28 06:46:26 +03:00
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 ( )
}
2026-05-18 14:52:58 +03:00
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 ( ) {
2026-06-04 15:45:53 +01:00
InstallController . updateServerConfig ( ServersUiController . processedServerId , ServersUiController . processedContainerIndex , ProtocolEnum . MtProxy , cp )
2026-05-18 14:52:58 +03:00
} )
}
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 ( )
} )
2026-05-28 06:46:26 +03:00
Qt . callLater ( root . mtProxyOnPageShown )
2026-05-18 14:52:58 +03:00
}
onNavigationBlockedWhileBusyChanged: {
if ( root . visible ) {
PageController . disableControls ( navigationBlockedWhileBusy )
}
}
onVisibleChanged: {
if ( ! visible ) {
2026-05-28 06:46:26 +03:00
root . pageOpenHandled = false
containerStatusRefreshCallPending = false
isCheckingStatus = false
isFetchingSecret = false
busyIndicatorShown = false
2026-05-18 14:52:58 +03:00
PageController . disableControls ( false )
2026-05-28 06:46:26 +03:00
PageController . showBusyIndicator ( false )
2026-05-18 14:52:58 +03:00
diagLoading = false
} else {
2026-05-28 06:46:26 +03:00
root . mtProxyOnPageShown ( )
2026-05-18 14:52:58 +03:00
}
}
Connections {
target: NetworkReachabilityController
function onHasInternetAccessChanged ( ) {
if ( ! root . visible ) {
return
}
if ( NetworkReachabilityController . hasInternetAccess ) {
2026-05-28 06:46:26 +03:00
root . mtProxyScheduleContainerStatusRefresh ( )
2026-05-18 14:52:58 +03:00
}
}
}
Connections {
target: InstallController
2026-05-28 06:46:26 +03:00
function onServerIsBusy ( busy ) {
remoteOperationBusy = busy
}
2026-05-18 14:52:58 +03:00
function onUpdateContainerFinished ( message , closePage ) {
if ( ! root . visible ) {
isUpdating = false
isCheckingStatus = false
2026-05-28 06:46:26 +03:00
isFetchingSecret = false
2026-05-18 14:52:58 +03:00
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
2026-05-28 06:46:26 +03:00
isFetchingSecret = false
2026-05-18 14:52:58 +03:00
return
}
isUpdating = false
2026-05-28 06:46:26 +03:00
isFetchingSecret = false
2026-05-18 14:52:58 +03:00
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
2026-05-28 06:46:26 +03:00
isUpdating = true
2026-05-18 14:52:58 +03:00
root . mtProxyScheduleUpdate ( false )
return
}
isUpdating = false
containerStatus = enabled ? 1 : 2
PageController . showNotificationMessage (
enabled ? qsTr ( "MTProxy started" ) : qsTr ( "MTProxy stopped" ) )
}
function onContainerStatusRefreshed ( status ) {
if ( ! root . visible ) {
isCheckingStatus = false
2026-05-28 06:46:26 +03:00
isFetchingSecret = false
2026-05-18 14:52:58 +03:00
return
}
containerStatus = status
root . savedTransportMode = MtProxyConfigModel . getTransportMode ( )
root . savedTlsDomain = MtProxyConfigModel . getTlsDomain ( )
root . savedPublicHost = MtProxyConfigModel . getPublicHost ( )
if ( status === 1 ) {
MtProxyConfigModel . setEnabled ( true )
2026-05-28 06:46:26 +03:00
isFetchingSecret = true
isCheckingStatus = false
2026-05-28 10:57:08 +08:00
InstallController . fetchContainerSecret ( ServersUiController . processedServerId , ServersUiController . processedContainerIndex )
2026-05-28 06:46:26 +03:00
} else {
isFetchingSecret = false
isCheckingStatus = false
if ( status === 2 ) {
MtProxyConfigModel . setEnabled ( false )
}
2026-05-18 14:52:58 +03:00
}
2026-05-28 06:46:26 +03:00
syncPageBusyIndicator ( )
2026-05-18 14:52:58 +03:00
}
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 ) {
2026-05-28 06:46:26 +03:00
isFetchingSecret = false
2026-05-18 14:52:58 +03:00
return
}
2026-05-28 06:46:26 +03:00
isFetchingSecret = false
syncPageBusyIndicator ( )
2026-05-18 14:52:58 +03:00
MtProxyConfigModel . validateAndSetSecret ( secret )
}
}
2026-05-28 06:46:26 +03:00
Item {
id: contentLayer
anchors.fill: parent
enabled: ! root . pageBusy
2026-05-18 14:52:58 +03:00
BackButtonType {
id: backButton
anchors.top: parent . top
anchors.left: parent . left
anchors.right: parent . right
2026-05-28 06:46:26 +03:00
anchors.topMargin: 20 + PageController . safeAreaTopMargin
2026-05-18 14:52:58 +03:00
onFocusChanged: {
2026-05-28 06:46:26 +03:00
if ( this . activeFocus ) {
if ( mainTabBar . currentIndex === 0 ) {
connectionListView . positionViewAtBeginning ( )
} else {
settingsListView . positionViewAtBeginning ( )
}
}
2026-05-18 14:52:58 +03:00
}
}
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
2026-05-28 06:46:26 +03:00
Layout.bottomMargin: 24
2026-05-18 14:52:58 +03:00
headerText: qsTr ( "MTProxy settings" )
2026-05-28 06:46:26 +03:00
descriptionLinkText: qsTr ( "Read more about this settings" )
descriptionLinkUrl: "https://core.telegram.org/proxy"
2026-05-18 14:52:58 +03:00
}
2026-05-28 06:46:26 +03:00
CaptionTextType {
2026-05-18 14:52:58 +03:00
Layout.fillWidth: true
2026-05-28 06:46:26 +03:00
Layout.leftMargin: 16
2026-05-18 14:52:58 +03:00
Layout.rightMargin: 16
2026-05-28 06:46:26 +03:00
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
2026-05-18 14:52:58 +03:00
}
2026-05-28 06:46:26 +03:00
}
2026-05-18 14:52:58 +03:00
2026-05-28 06:46:26 +03:00
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
2026-05-18 14:52:58 +03:00
}
2026-05-28 06:46:26 +03:00
}
2026-05-18 14:52:58 +03:00
2026-05-28 06:46:26 +03:00
TabButtonType {
text: qsTr ( "Connection" )
isSelected: mainTabBar . currentIndex === 0
}
TabButtonType {
text: qsTr ( "Settings" )
isSelected: mainTabBar . currentIndex === 1
2026-05-18 14:52:58 +03:00
}
}
StackLayout {
id: tabContent
2026-05-28 06:46:26 +03:00
anchors.top: mainTabBar . bottom
2026-05-18 14:52:58 +03:00
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 ( ) {
2026-05-28 06:46:26 +03:00
return root . mtProxyClientSecretForTabIndex ( secret , root . syncedSecretTabIndex ,
root . savedTlsDomain , MtProxyConfigModel . defaultTlsDomain ( ) )
2026-05-18 14:52:58 +03:00
}
function effectiveSecret ( ) {
return activeSecret ( )
}
function effectiveHost ( ) {
2026-05-28 10:57:08 +08:00
return root . savedPublicHost !== "" ? root.savedPublicHost : ServersUiController . serverHostName ( ServersUiController . processedServerId )
2026-05-18 14:52:58 +03:00
}
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
2026-05-28 10:57:08 +08:00
visible: ServersUiController . isProcessedServerHasWriteAccess ( )
2026-05-18 14:52:58 +03:00
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 )
2026-05-28 10:57:08 +08:00
InstallController . removeContainer ( ServersUiController . processedServerId , ServersUiController . processedContainerIndex )
2026-05-18 14:52:58 +03:00
}
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
function mtProxyActiveSecretForBaseHex ( baseHex ) {
2026-05-28 06:46:26 +03:00
return root . mtProxyClientSecretForTabIndex ( baseHex , root . syncedSecretTabIndex ,
root . savedTlsDomain , MtProxyConfigModel . defaultTlsDomain ( ) )
2026-05-18 14:52:58 +03:00
}
function mtProxyEffectiveHostForLinks ( ) {
2026-05-28 10:57:08 +08:00
return root . savedPublicHost !== "" ? root.savedPublicHost : ServersUiController . serverHostName ( ServersUiController . processedServerId )
2026-05-18 14:52:58 +03:00
}
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
2026-05-28 06:46:26 +03:00
enabled: containerStatus !== 0 && containerStatus !== 3 && ! root . pageBusy
2026-05-18 14:52:58 +03:00
&& ! root . mtProxyNetworkBlocked
onToggled: function ( ) {
if ( checked !== isEnabled ) {
previousEnabled = isEnabled
previousContainerStatus = containerStatus
root . previousSecret = secret
isEnabled = checked
isUpdating = true
if ( checked ) {
root . pendingUpdateAfterEnable = true
2026-05-28 10:57:08 +08:00
InstallController . setContainerEnabled ( ServersUiController . processedServerId , ServersUiController . processedContainerIndex , true )
2026-05-18 14:52:58 +03:00
} else {
2026-05-28 10:57:08 +08:00
InstallController . setContainerEnabled ( ServersUiController . processedServerId , ServersUiController . processedContainerIndex , false )
2026-05-18 14:52:58 +03:00
}
}
}
}
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
2026-05-28 06:46:26 +03:00
text: secret !== "" ? mtProxyActiveSecretForBaseHex ( secret ) : qsTr ( "Not generated" )
2026-05-18 14:52:58 +03:00
color: secret !== "" ? AmneziaStyle.color.paleGray : AmneziaStyle . color . mutedGray
2026-05-28 06:46:26 +03:00
wrapMode: Text . WrapAnywhere
2026-05-18 14:52:58 +03:00
font.pixelSize: 14
}
ImageButtonType {
2026-05-28 06:46:26 +03:00
Layout.alignment: Qt . AlignTop
2026-05-18 14:52:58 +03:00
implicitWidth: 36
implicitHeight: 36
hoverEnabled: true
image: "qrc:/images/controls/refresh-cw.svg"
imageColor: AmneziaStyle . color . paleGray
2026-05-28 10:57:08 +08:00
visible: ServersUiController . isProcessedServerHasWriteAccess ( )
2026-05-18 14:52:58 +03:00
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
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: 4
headerText: qsTr ( "Public host / IP" )
2026-05-28 10:57:08 +08:00
textField.placeholderText: ServersUiController . serverHostName ( ServersUiController . processedServerId )
2026-05-18 14:52:58 +03:00
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 !== "" &&
2026-05-28 10:57:08 +08:00
publicHostTextField . textField . text !== ServersUiController . serverHostName ( ServersUiController . processedServerId )
2026-05-18 14:52:58 +03:00
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
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: 16
headerText: qsTr ( "Server port" )
textField.placeholderText: MtProxyConfigModel . defaultPort ( )
textField.maximumLength: 5
textField.validator: IntValidator {
bottom: 1
top: 65535
}
Component.onCompleted: {
var savedPort = port
textField . text = ( savedPort === MtProxyConfigModel . defaultPort ( ) ) ? "" : savedPort
}
textField.onEditingFinished: {
textField . text = textField . text . replace ( /^\s+|\s+$/g , '' )
}
}
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
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
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 )
2026-05-28 06:46:26 +03:00
root . syncedSecretTabIndex = transportMode === "faketls" ? 1 : 0
2026-05-18 14:52:58 +03:00
transportModeDropDown . closeTriggered ( )
}
}
}
}
}
TextFieldWithHeaderType {
id: tlsDomainTextField
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: 16
visible: transportMode === "faketls"
headerText: qsTr ( "FakeTLS domain" )
textField.placeholderText: root . previousTlsDomain
Component.onCompleted: {
var savedDomain = tlsDomain
textField . text = ( savedDomain === MtProxyConfigModel . defaultTlsDomain ( ) || savedDomain === "" ) ? "" : savedDomain
}
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
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
2026-05-28 10:57:08 +08:00
visible: ServersUiController . isProcessedServerHasWriteAccess ( )
2026-05-18 14:52:58 +03:00
image: "qrc:/images/controls/trash.svg"
imageColor: AmneziaStyle . color . vibrantRed
onClicked: {
MtProxyConfigModel . removeAdditionalSecret ( index )
2026-05-28 06:46:26 +03:00
if ( containerStatus === 1 ) {
root . mtProxyScheduleUpdate ( false )
}
2026-05-18 14:52:58 +03:00
}
}
}
}
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: 3
textField.validator: IntValidator {
bottom: 1
top: MtProxyConfigModel . maxWorkers ( )
}
textField.onEditingFinished: {
textField . text = textField . text . replace ( /^\s+|\s+$/g , '' )
if ( textField . text !== workers ) {
workers = textField . text
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
2026-05-28 10:57:08 +08:00
InstallController . refreshContainerDiagnostics ( ServersUiController . processedServerId , ServersUiController . processedContainerIndex , parseInt ( port ) )
2026-05-18 14:52:58 +03:00
}
}
}
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
2026-05-28 10:57:08 +08:00
visible: ServersUiController . isProcessedServerHasWriteAccess ( )
2026-05-18 14:52:58 +03:00
enabled: ! 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 )
}
}
}
}
}
}
}