mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-23 02:00:20 +07:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d915ba1e77 | |||
| 578ec6d6a8 | |||
| 5b88b18e9d | |||
| db57aa29a9 | |||
| 794360fd0f | |||
| 9a84202a62 | |||
| 977a393e12 | |||
| 07aad87874 | |||
| 6202078e0c | |||
| 4cc07acae5 | |||
| 0d99459670 | |||
| b5c047565c | |||
| 60904b9fde | |||
| cc404378f9 |
@@ -103,7 +103,7 @@ ErrorCode InstallController::setupContainer(const ServerCredentials &credentials
|
|||||||
bool isUpdate)
|
bool isUpdate)
|
||||||
{
|
{
|
||||||
qDebug().noquote() << "InstallController::setupContainer" << ContainerUtils::containerToString(container);
|
qDebug().noquote() << "InstallController::setupContainer" << ContainerUtils::containerToString(container);
|
||||||
SshSession sshSession(this);
|
SshSession sshSession;
|
||||||
ErrorCode e = ErrorCode::NoError;
|
ErrorCode e = ErrorCode::NoError;
|
||||||
|
|
||||||
e = isUserInSudo(credentials, sshSession);
|
e = isUserInSudo(credentials, sshSession);
|
||||||
@@ -168,11 +168,11 @@ ErrorCode InstallController::updateServerConfig(const QString &serverId, DockerC
|
|||||||
}
|
}
|
||||||
if (container == DockerContainer::MtProxy) {
|
if (container == DockerContainer::MtProxy) {
|
||||||
ServerCredentials credentials = adminConfig->credentials();
|
ServerCredentials credentials = adminConfig->credentials();
|
||||||
SshSession sshSession(this);
|
SshSession sshSession;
|
||||||
MtProxyInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
|
MtProxyInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
|
||||||
} else if (container == DockerContainer::Telemt) {
|
} else if (container == DockerContainer::Telemt) {
|
||||||
ServerCredentials credentials = adminConfig->credentials();
|
ServerCredentials credentials = adminConfig->credentials();
|
||||||
SshSession sshSession(this);
|
SshSession sshSession;
|
||||||
TelemtInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
|
TelemtInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
|
||||||
}
|
}
|
||||||
adminConfig->updateContainerConfig(container, newConfig);
|
adminConfig->updateContainerConfig(container, newConfig);
|
||||||
@@ -188,7 +188,7 @@ ErrorCode InstallController::updateServerConfig(const QString &serverId, DockerC
|
|||||||
if (!credentials.isValid()) {
|
if (!credentials.isValid()) {
|
||||||
return ErrorCode::InternalError;
|
return ErrorCode::InternalError;
|
||||||
}
|
}
|
||||||
SshSession sshSession(this);
|
SshSession sshSession;
|
||||||
|
|
||||||
bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig);
|
bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig);
|
||||||
qDebug() << "InstallController::updateServerConfig for container" << container << "reinstall required is" << reinstallRequired;
|
qDebug() << "InstallController::updateServerConfig for container" << container << "reinstall required is" << reinstallRequired;
|
||||||
@@ -211,6 +211,12 @@ ErrorCode InstallController::updateServerConfig(const QString &serverId, DockerC
|
|||||||
if (errorCode == ErrorCode::NoError) {
|
if (errorCode == ErrorCode::NoError) {
|
||||||
errorCode = startupContainerWorker(credentials, container, newConfig, sshSession);
|
errorCode = startupContainerWorker(credentials, container, newConfig, sshSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (errorCode == ErrorCode::NoError
|
||||||
|
&& (container == DockerContainer::MtProxy || container == DockerContainer::Telemt)) {
|
||||||
|
const QString containerName = ContainerUtils::containerToString(container);
|
||||||
|
errorCode = sshSession.runScript(credentials, "sudo docker restart " + containerName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool skipXrayInboundSync =
|
const bool skipXrayInboundSync =
|
||||||
@@ -737,18 +743,6 @@ bool InstallController::isReinstallContainerRequired(DockerContainer container,
|
|||||||
if (oldPort != newPort) {
|
if (oldPort != newPort) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const QString oldTransport = oldMt->transportMode.isEmpty() ? QString(
|
|
||||||
protocols::mtProxy::transportModeStandard)
|
|
||||||
: oldMt->transportMode;
|
|
||||||
const QString newTransport = newMt->transportMode.isEmpty() ? QString(
|
|
||||||
protocols::mtProxy::transportModeStandard)
|
|
||||||
: newMt->transportMode;
|
|
||||||
if (oldTransport != newTransport) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (oldMt->tlsDomain != newMt->tlsDomain) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -763,39 +757,6 @@ bool InstallController::isReinstallContainerRequired(DockerContainer container,
|
|||||||
if (oldPort != newPort) {
|
if (oldPort != newPort) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const QString oldTransport = oldT->transportMode.isEmpty()
|
|
||||||
? QString(protocols::telemt::transportModeStandard)
|
|
||||||
: oldT->transportMode;
|
|
||||||
const QString newTransport = newT->transportMode.isEmpty()
|
|
||||||
? QString(protocols::telemt::transportModeStandard)
|
|
||||||
: newT->transportMode;
|
|
||||||
if (oldTransport != newTransport) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (oldT->tlsDomain != newT->tlsDomain) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (oldT->maskEnabled != newT->maskEnabled) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (oldT->tlsEmulation != newT->tlsEmulation) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (oldT->useMiddleProxy != newT->useMiddleProxy) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (oldT->tag != newT->tag) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const QString oldUser = oldT->userName.isEmpty()
|
|
||||||
? QString::fromUtf8(protocols::telemt::defaultUserName)
|
|
||||||
: oldT->userName;
|
|
||||||
const QString newUser = newT->userName.isEmpty()
|
|
||||||
? QString::fromUtf8(protocols::telemt::defaultUserName)
|
|
||||||
: newT->userName;
|
|
||||||
if (oldUser != newUser) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -835,6 +796,20 @@ ErrorCode InstallController::installDockerWorker(const ServerCredentials &creden
|
|||||||
|
|
||||||
qDebug().noquote() << "InstallController::installDockerWorker" << stdOut;
|
qDebug().noquote() << "InstallController::installDockerWorker" << stdOut;
|
||||||
|
|
||||||
|
if (container == DockerContainer::MtProxy || container == DockerContainer::Telemt) {
|
||||||
|
QString conntrackOut;
|
||||||
|
auto cbConntrack = [&](const QString &data, libssh::Client &) {
|
||||||
|
conntrackOut += data + "\n";
|
||||||
|
return ErrorCode::NoError;
|
||||||
|
};
|
||||||
|
sshSession.runScript(
|
||||||
|
credentials,
|
||||||
|
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::install_conntrack),
|
||||||
|
amnezia::genBaseVars(credentials, DockerContainer::None, QString(), QString())),
|
||||||
|
cbConntrack, cbConntrack);
|
||||||
|
qDebug().noquote() << "InstallController::installDockerWorker install_conntrack:" << conntrackOut;
|
||||||
|
}
|
||||||
|
|
||||||
if (container == DockerContainer::Awg2) {
|
if (container == DockerContainer::Awg2) {
|
||||||
QRegularExpression regex(R"(Linux\s+(\d+)\.(\d+)[^\d]*)");
|
QRegularExpression regex(R"(Linux\s+(\d+)\.(\d+)[^\d]*)");
|
||||||
QRegularExpressionMatch match = regex.match(stdOut);
|
QRegularExpressionMatch match = regex.match(stdOut);
|
||||||
@@ -970,7 +945,7 @@ ErrorCode InstallController::rebootServer(const QString &serverId)
|
|||||||
if (!credentials.isValid()) {
|
if (!credentials.isValid()) {
|
||||||
return ErrorCode::InternalError;
|
return ErrorCode::InternalError;
|
||||||
}
|
}
|
||||||
SshSession sshSession(this);
|
SshSession sshSession;
|
||||||
|
|
||||||
QString script = QString("sudo reboot");
|
QString script = QString("sudo reboot");
|
||||||
|
|
||||||
@@ -998,7 +973,7 @@ ErrorCode InstallController::removeAllContainers(const QString &serverId)
|
|||||||
if (!credentials.isValid()) {
|
if (!credentials.isValid()) {
|
||||||
return ErrorCode::InternalError;
|
return ErrorCode::InternalError;
|
||||||
}
|
}
|
||||||
SshSession sshSession(this);
|
SshSession sshSession;
|
||||||
ErrorCode errorCode = sshSession.runScript(credentials, amnezia::scriptData(SharedScriptType::remove_all_containers));
|
ErrorCode errorCode = sshSession.runScript(credentials, amnezia::scriptData(SharedScriptType::remove_all_containers));
|
||||||
|
|
||||||
if (errorCode == ErrorCode::NoError) {
|
if (errorCode == ErrorCode::NoError) {
|
||||||
@@ -1020,7 +995,7 @@ ErrorCode InstallController::removeContainer(const QString &serverId, DockerCont
|
|||||||
if (!credentials.isValid()) {
|
if (!credentials.isValid()) {
|
||||||
return ErrorCode::InternalError;
|
return ErrorCode::InternalError;
|
||||||
}
|
}
|
||||||
SshSession sshSession(this);
|
SshSession sshSession;
|
||||||
const amnezia::ScriptVars removeContainerVars =
|
const amnezia::ScriptVars removeContainerVars =
|
||||||
amnezia::genBaseVars(credentials, container, QString(), QString());
|
amnezia::genBaseVars(credentials, container, QString(), QString());
|
||||||
const bool removeDataVolume = (container == DockerContainer::MtProxy || container == DockerContainer::Telemt);
|
const bool removeDataVolume = (container == DockerContainer::MtProxy || container == DockerContainer::Telemt);
|
||||||
@@ -1129,7 +1104,7 @@ ErrorCode InstallController::scanServerForInstalledContainers(const QString &ser
|
|||||||
if (!credentials.isValid()) {
|
if (!credentials.isValid()) {
|
||||||
return ErrorCode::InternalError;
|
return ErrorCode::InternalError;
|
||||||
}
|
}
|
||||||
SshSession sshSession(this);
|
SshSession sshSession;
|
||||||
|
|
||||||
QMap<DockerContainer, ContainerConfig> installedContainers;
|
QMap<DockerContainer, ContainerConfig> installedContainers;
|
||||||
ErrorCode errorCode = getAlreadyInstalledContainers(credentials, installedContainers, sshSession);
|
ErrorCode errorCode = getAlreadyInstalledContainers(credentials, installedContainers, sshSession);
|
||||||
@@ -1172,7 +1147,7 @@ ErrorCode InstallController::scanServerForInstalledContainers(const QString &ser
|
|||||||
ErrorCode InstallController::installServer(const ServerCredentials &credentials, DockerContainer container, int port,
|
ErrorCode InstallController::installServer(const ServerCredentials &credentials, DockerContainer container, int port,
|
||||||
TransportProto transportProto, bool &wasContainerInstalled)
|
TransportProto transportProto, bool &wasContainerInstalled)
|
||||||
{
|
{
|
||||||
SshSession sshSession(this);
|
SshSession sshSession;
|
||||||
QMap<DockerContainer, ContainerConfig> installedContainers;
|
QMap<DockerContainer, ContainerConfig> installedContainers;
|
||||||
ErrorCode errorCode = getAlreadyInstalledContainers(credentials, installedContainers, sshSession);
|
ErrorCode errorCode = getAlreadyInstalledContainers(credentials, installedContainers, sshSession);
|
||||||
if (errorCode) {
|
if (errorCode) {
|
||||||
@@ -1241,7 +1216,7 @@ ErrorCode InstallController::installContainer(const QString &serverId, DockerCon
|
|||||||
if (!credentials.isValid()) {
|
if (!credentials.isValid()) {
|
||||||
return ErrorCode::InternalError;
|
return ErrorCode::InternalError;
|
||||||
}
|
}
|
||||||
SshSession sshSession(this);
|
SshSession sshSession;
|
||||||
|
|
||||||
QMap<DockerContainer, ContainerConfig> installedContainers;
|
QMap<DockerContainer, ContainerConfig> installedContainers;
|
||||||
ErrorCode errorCode = getAlreadyInstalledContainers(credentials, installedContainers, sshSession);
|
ErrorCode errorCode = getAlreadyInstalledContainers(credentials, installedContainers, sshSession);
|
||||||
@@ -1283,7 +1258,7 @@ ErrorCode InstallController::installContainer(const QString &serverId, DockerCon
|
|||||||
ErrorCode InstallController::checkSshConnection(ServerCredentials &credentials, QString &output,
|
ErrorCode InstallController::checkSshConnection(ServerCredentials &credentials, QString &output,
|
||||||
std::function<QString()> passphraseCallback)
|
std::function<QString()> passphraseCallback)
|
||||||
{
|
{
|
||||||
SshSession sshSession(this);
|
SshSession sshSession;
|
||||||
ErrorCode errorCode = ErrorCode::NoError;
|
ErrorCode errorCode = ErrorCode::NoError;
|
||||||
|
|
||||||
if (credentials.secretData.contains("BEGIN") && credentials.secretData.contains("PRIVATE KEY")) {
|
if (credentials.secretData.contains("BEGIN") && credentials.secretData.contains("PRIVATE KEY")) {
|
||||||
@@ -1564,7 +1539,7 @@ ErrorCode InstallController::setDockerContainerEnabledState(const QString &serve
|
|||||||
return ErrorCode::InternalError;
|
return ErrorCode::InternalError;
|
||||||
}
|
}
|
||||||
const QString containerName = ContainerUtils::containerToString(container);
|
const QString containerName = ContainerUtils::containerToString(container);
|
||||||
SshSession sshSession(this);
|
SshSession sshSession;
|
||||||
const QString script = enabled ? QStringLiteral("sudo docker start %1").arg(containerName)
|
const QString script = enabled ? QStringLiteral("sudo docker start %1").arg(containerName)
|
||||||
: QStringLiteral("sudo docker stop %1").arg(containerName);
|
: QStringLiteral("sudo docker stop %1").arg(containerName);
|
||||||
const ErrorCode runError = sshSession.runScript(credentials, script);
|
const ErrorCode runError = sshSession.runScript(credentials, script);
|
||||||
@@ -1604,7 +1579,7 @@ ErrorCode InstallController::queryDockerContainerStatus(const QString &serverId,
|
|||||||
stdOut += data;
|
stdOut += data;
|
||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
};
|
};
|
||||||
SshSession sshSession(this);
|
SshSession sshSession;
|
||||||
const QString script = QStringLiteral(
|
const QString script = QStringLiteral(
|
||||||
"sudo docker inspect --format '{{.State.Status}}' %1 2>/dev/null || echo 'not_found'")
|
"sudo docker inspect --format '{{.State.Status}}' %1 2>/dev/null || echo 'not_found'")
|
||||||
.arg(containerName);
|
.arg(containerName);
|
||||||
@@ -1638,7 +1613,7 @@ ErrorCode InstallController::queryMtProxyDiagnostics(const QString &serverId, Do
|
|||||||
if (!credentials.isValid()) {
|
if (!credentials.isValid()) {
|
||||||
return ErrorCode::InternalError;
|
return ErrorCode::InternalError;
|
||||||
}
|
}
|
||||||
SshSession sshSession(this);
|
SshSession sshSession;
|
||||||
return MtProxyInstaller::queryDiagnostics(sshSession, credentials, container, listenPort, out);
|
return MtProxyInstaller::queryDiagnostics(sshSession, credentials, container, listenPort, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1661,7 +1636,7 @@ QString InstallController::fetchDockerContainerSecret(const QString &serverId, D
|
|||||||
stdOut += data;
|
stdOut += data;
|
||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
};
|
};
|
||||||
SshSession sshSession(this);
|
SshSession sshSession;
|
||||||
const QString path = QStringLiteral("/data/secret");
|
const QString path = QStringLiteral("/data/secret");
|
||||||
const QString cmd = QStringLiteral("sudo docker exec %1 cat %2").arg(containerName, path);
|
const QString cmd = QStringLiteral("sudo docker exec %1 cat %2").arg(containerName, path);
|
||||||
const ErrorCode errorCode = sshSession.runScript(credentials, cmd, cbReadStdOut);
|
const ErrorCode errorCode = sshSession.runScript(credentials, cmd, cbReadStdOut);
|
||||||
|
|||||||
@@ -71,24 +71,35 @@ ErrorCode MtProxyInstaller::queryDiagnostics(SshSession &sshSession, const Serve
|
|||||||
DockerContainer container, int listenPort,
|
DockerContainer container, int listenPort,
|
||||||
MtProxyContainerDiagnostics &out)
|
MtProxyContainerDiagnostics &out)
|
||||||
{
|
{
|
||||||
out = {};
|
out = { };
|
||||||
if (container != DockerContainer::MtProxy && container != DockerContainer::Telemt) {
|
if (container == DockerContainer::MtProxy || container == DockerContainer::Telemt) {
|
||||||
return ErrorCode::InternalError;
|
|
||||||
}
|
|
||||||
const QString containerName = ContainerUtils::containerToString(container);
|
const QString containerName = ContainerUtils::containerToString(container);
|
||||||
const QString script =
|
const bool isTelemt = container == DockerContainer::Telemt;
|
||||||
QStringLiteral(
|
|
||||||
"PORT_OK=$(sudo docker exec %1 sh -c 'ss -tlnp 2>/dev/null | grep -q :%2 && echo yes || echo no' 2>/dev/null || echo no); "
|
const QString sportFilter = QString::number(listenPort);
|
||||||
"TG_OK=$(curl -s --max-time 5 -o /dev/null -w '%%{http_code}' https://core.telegram.org/getProxySecret 2>/dev/null | grep -q '200' && echo yes || echo no); "
|
const QString peersCmd = QStringLiteral("sudo conntrack -L -p tcp --dport ") + sportFilter
|
||||||
"CLIENTS=$(sudo docker exec amnezia-mtproxy sh -c 'curl -s --max-time 3 http://localhost:2398/stats 2>/dev/null | grep -o \"total_special_connections:[0-9]*\" | cut -d: -f2' 2>/dev/null); "
|
+ QStringLiteral(" 2>/dev/null | grep ESTABLISHED | awk '{for(i=1;i<=NF;i++) if($i ~ /^src=/){print "
|
||||||
"CONF_TIME=$(sudo docker exec amnezia-mtproxy sh -c 'stat -c \"%%y\" /data/proxy-multi.conf 2>/dev/null | cut -d. -f1' 2>/dev/null || echo unknown); "
|
"substr($i,5); break}}'");
|
||||||
"echo \"PORT_OK=${PORT_OK}\"; "
|
const QString publicFilter = QStringLiteral(" | grep -vE "
|
||||||
"echo \"TG_OK=${TG_OK}\"; "
|
"'^(10\\.|127\\.|169\\.254\\.|192\\.168\\.|172\\.(1[6-9]|2[0-9]|3["
|
||||||
"echo \"CLIENTS=${CLIENTS:-0}\"; "
|
"01])\\.|::1$|fe80:|f[cd][0-9a-f][0-9a-f]:)'");
|
||||||
"echo \"CONF_TIME=${CONF_TIME}\"; "
|
const QString clientsCmd =
|
||||||
"echo \"STATS=http://localhost:2398/stats\";")
|
QStringLiteral("CLIENTS=$(") + peersCmd + publicFilter + QStringLiteral(" | sort -u | grep -c .); ");
|
||||||
.arg(containerName)
|
const QString confFile =
|
||||||
.arg(listenPort);
|
isTelemt ? QStringLiteral("/data/config.toml") : QStringLiteral("/data/proxy-multi.conf");
|
||||||
|
const QString statsUrl = QString();
|
||||||
|
|
||||||
|
const QString script = QStringLiteral("CN=") + containerName + QStringLiteral("; ")
|
||||||
|
+ QStringLiteral("PORT_OK=$(sudo ss -tlnp 2>/dev/null | grep -q :") + QString::number(listenPort)
|
||||||
|
+ QStringLiteral(" && echo yes || echo no); ")
|
||||||
|
+ QStringLiteral("TG_OK=$(curl -s --max-time 5 -o /dev/null -w '%{http_code}' "
|
||||||
|
"https://core.telegram.org/getProxySecret 2>/dev/null | grep -q '200' && echo yes || "
|
||||||
|
"echo no); ")
|
||||||
|
+ clientsCmd + QStringLiteral("CONF_TIME=$(sudo docker exec \"$CN\" sh -c 'stat -c \"%y\" ") + confFile
|
||||||
|
+ QStringLiteral(" 2>/dev/null | cut -d. -f1' 2>/dev/null || echo unknown); ")
|
||||||
|
+ QStringLiteral("echo \"PORT_OK=${PORT_OK}\"; ") + QStringLiteral("echo \"TG_OK=${TG_OK}\"; ")
|
||||||
|
+ QStringLiteral("echo \"CLIENTS=${CLIENTS:-0}\"; ") + QStringLiteral("echo \"CONF_TIME=${CONF_TIME}\"; ")
|
||||||
|
+ QStringLiteral("echo \"STATS=") + statsUrl + QStringLiteral("\";");
|
||||||
|
|
||||||
QString stdOut;
|
QString stdOut;
|
||||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||||
@@ -113,6 +124,9 @@ ErrorCode MtProxyInstaller::queryDiagnostics(SshSession &sshSession, const Serve
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ErrorCode::InternalError;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MtProxyInstaller::uploadClientSettingsSnapshot(SshSession &sshSession, const ServerCredentials &credentials,
|
void MtProxyInstaller::uploadClientSettingsSnapshot(SshSession &sshSession, const ServerCredentials &credentials,
|
||||||
|
|||||||
@@ -271,6 +271,7 @@ namespace amnezia
|
|||||||
constexpr char workersModeAuto[] = "auto";
|
constexpr char workersModeAuto[] = "auto";
|
||||||
constexpr char workersModeManual[] = "manual";
|
constexpr char workersModeManual[] = "manual";
|
||||||
constexpr int maxWorkers = 32;
|
constexpr int maxWorkers = 32;
|
||||||
|
constexpr int botTagHexLength = 32;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace protocols
|
} // namespace protocols
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ QString amnezia::scriptName(SharedScriptType type)
|
|||||||
switch (type) {
|
switch (type) {
|
||||||
case SharedScriptType::prepare_host: return QLatin1String("prepare_host.sh");
|
case SharedScriptType::prepare_host: return QLatin1String("prepare_host.sh");
|
||||||
case SharedScriptType::install_docker: return QLatin1String("install_docker.sh");
|
case SharedScriptType::install_docker: return QLatin1String("install_docker.sh");
|
||||||
|
case SharedScriptType::install_conntrack: return QLatin1String("install_conntrack.sh");
|
||||||
case SharedScriptType::build_container: return QLatin1String("build_container.sh");
|
case SharedScriptType::build_container: return QLatin1String("build_container.sh");
|
||||||
case SharedScriptType::remove_container: return QLatin1String("remove_container.sh");
|
case SharedScriptType::remove_container: return QLatin1String("remove_container.sh");
|
||||||
case SharedScriptType::remove_all_containers: return QLatin1String("remove_all_containers.sh");
|
case SharedScriptType::remove_all_containers: return QLatin1String("remove_all_containers.sh");
|
||||||
@@ -366,6 +367,14 @@ amnezia::ScriptVars amnezia::genTelemtVars(const ContainerConfig &containerConfi
|
|||||||
vars.append({ { "$TELEMT_USE_MIDDLE_PROXY", c.useMiddleProxy ? QLatin1String("true") : QLatin1String("false") } });
|
vars.append({ { "$TELEMT_USE_MIDDLE_PROXY", c.useMiddleProxy ? QLatin1String("true") : QLatin1String("false") } });
|
||||||
vars.append({ { "$TELEMT_MASK", c.maskEnabled ? QLatin1String("true") : QLatin1String("false") } });
|
vars.append({ { "$TELEMT_MASK", c.maskEnabled ? QLatin1String("true") : QLatin1String("false") } });
|
||||||
vars.append({ { "$TELEMT_TLS_EMULATION", c.tlsEmulation ? QLatin1String("true") : QLatin1String("false") } });
|
vars.append({ { "$TELEMT_TLS_EMULATION", c.tlsEmulation ? QLatin1String("true") : QLatin1String("false") } });
|
||||||
|
|
||||||
|
QStringList additionalList;
|
||||||
|
for (const QString &s : c.additionalSecrets) {
|
||||||
|
if (!s.isEmpty()) {
|
||||||
|
additionalList << s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vars.append({ { "$TELEMT_ADDITIONAL_SECRETS", additionalList.join(QLatin1Char(',')) } });
|
||||||
}
|
}
|
||||||
|
|
||||||
return vars;
|
return vars;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ enum SharedScriptType {
|
|||||||
// General scripts
|
// General scripts
|
||||||
prepare_host,
|
prepare_host,
|
||||||
install_docker,
|
install_docker,
|
||||||
|
install_conntrack,
|
||||||
build_container,
|
build_container,
|
||||||
remove_container,
|
remove_container,
|
||||||
remove_all_containers,
|
remove_all_containers,
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
if command -v conntrack > /dev/null 2>&1; then echo "conntrack already installed"; exit 0; fi;\
|
||||||
|
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); silent_inst="-yq install --install-recommends"; check_pkgs="-yq update"; conntrack_pkg="conntrack"; dist="debian";\
|
||||||
|
elif which dnf > /dev/null 2>&1; then pm=$(which dnf); silent_inst="-yq install"; check_pkgs="-yq check-update"; conntrack_pkg="conntrack-tools"; dist="fedora";\
|
||||||
|
elif which yum > /dev/null 2>&1; then pm=$(which yum); silent_inst="-y -q install"; check_pkgs="-y -q check-update"; conntrack_pkg="conntrack-tools"; dist="centos";\
|
||||||
|
elif which zypper > /dev/null 2>&1; then pm=$(which zypper); silent_inst="-nq install"; check_pkgs="-nq refresh"; conntrack_pkg="conntrack-tools"; dist="opensuse";\
|
||||||
|
elif which pacman > /dev/null 2>&1; then pm=$(which pacman); silent_inst="-S --noconfirm --noprogressbar --quiet"; check_pkgs="-Sup"; conntrack_pkg="conntrack-tools"; dist="archlinux";\
|
||||||
|
else echo "Packet manager not found"; exit 0; fi;\
|
||||||
|
if [ "$dist" = "debian" ]; then export DEBIAN_FRONTEND=noninteractive; fi;\
|
||||||
|
sudo $pm $check_pkgs; sudo $pm $silent_inst $conntrack_pkg;\
|
||||||
|
command -v conntrack > /dev/null 2>&1 && echo "conntrack installed" || echo "conntrack install failed"
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker stop;\
|
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker stop;\
|
||||||
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker rm -fv;\
|
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker rm -fv;\
|
||||||
sudo docker images -a --format table | grep amnezia | awk '{print $3, $1 ":" $2}' | xargs sudo docker rmi;\
|
sudo docker images -a --format table | grep amnezia | awk '{print $3, $1 ":" $2}' | xargs sudo docker rmi;\
|
||||||
sudo docker volume ls | grep amnezia | awk '{print $2}' | xargs sudo docker volume rm -f;\
|
sudo docker volume ls --format '{{.Name}}' | grep '^amnezia-' | xargs -r sudo docker volume rm -f;\
|
||||||
sudo docker network ls | grep amnezia-dns-net | awk '{print $1}' | xargs sudo docker network rm;\
|
sudo docker network ls | grep amnezia-dns-net | awk '{print $1}' | xargs sudo docker network rm;\
|
||||||
sudo rm -frd /opt/amnezia
|
sudo rm -frd /opt/amnezia
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
<file>dns/Dockerfile</file>
|
<file>dns/Dockerfile</file>
|
||||||
<file>dns/run_container.sh</file>
|
<file>dns/run_container.sh</file>
|
||||||
<file>install_docker.sh</file>
|
<file>install_docker.sh</file>
|
||||||
|
<file>install_conntrack.sh</file>
|
||||||
<file>ipsec/configure_container.sh</file>
|
<file>ipsec/configure_container.sh</file>
|
||||||
<file>ipsec/Dockerfile</file>
|
<file>ipsec/Dockerfile</file>
|
||||||
<file>ipsec/mobileconfig.plist</file>
|
<file>ipsec/mobileconfig.plist</file>
|
||||||
|
|||||||
@@ -61,6 +61,12 @@ rm -f /data/config.toml
|
|||||||
echo ""
|
echo ""
|
||||||
echo "[access.users]"
|
echo "[access.users]"
|
||||||
echo "$TELEMT_USER_NAME = \"$SECRET\""
|
echo "$TELEMT_USER_NAME = \"$SECRET\""
|
||||||
|
i=1
|
||||||
|
for EXTRA in $(echo "$TELEMT_ADDITIONAL_SECRETS" | tr ',' ' '); do
|
||||||
|
echo "$EXTRA" | grep -qE '^[0-9a-fA-F]{32}$' || continue
|
||||||
|
echo "extra_$i = \"$EXTRA\""
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
} > /data/config.toml
|
} > /data/config.toml
|
||||||
|
|
||||||
echo "$SECRET" > /data/secret
|
echo "$SECRET" > /data/secret
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <QFutureWatcher>
|
#include <QFutureWatcher>
|
||||||
#include <QtConcurrent>
|
#include <QtConcurrent>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include "core/utils/api/apiUtils.h"
|
#include "core/utils/api/apiUtils.h"
|
||||||
#include "core/controllers/selfhosted/installController.h"
|
#include "core/controllers/selfhosted/installController.h"
|
||||||
@@ -359,7 +360,13 @@ void InstallUiController::setContainerEnabled(const QString &serverId, int conta
|
|||||||
}
|
}
|
||||||
|
|
||||||
emit serverIsBusy(true);
|
emit serverIsBusy(true);
|
||||||
const ErrorCode errorCode = m_installController->setDockerContainerEnabledState(serverId, container, enabled);
|
|
||||||
|
InstallController *installController = m_installController;
|
||||||
|
auto *watcher = new QFutureWatcher<ErrorCode>(this);
|
||||||
|
QObject::connect(watcher, &QFutureWatcher<ErrorCode>::finished, this,
|
||||||
|
[this, watcher, serverId, container, enabled]() {
|
||||||
|
const ErrorCode errorCode = watcher->result();
|
||||||
|
watcher->deleteLater();
|
||||||
emit serverIsBusy(false);
|
emit serverIsBusy(false);
|
||||||
|
|
||||||
if (errorCode == ErrorCode::NoError) {
|
if (errorCode == ErrorCode::NoError) {
|
||||||
@@ -368,8 +375,12 @@ void InstallUiController::setContainerEnabled(const QString &serverId, int conta
|
|||||||
emit setContainerEnabledFinished(enabled);
|
emit setContainerEnabledFinished(enabled);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
emit installationErrorOccurred(errorCode);
|
emit installationErrorOccurred(errorCode);
|
||||||
|
});
|
||||||
|
QFuture<ErrorCode> future = QtConcurrent::run([installController, serverId, container, enabled]() -> ErrorCode {
|
||||||
|
return installController->setDockerContainerEnabledState(serverId, container, enabled);
|
||||||
|
});
|
||||||
|
watcher->setFuture(future);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstallUiController::refreshContainerStatus(const QString &serverId, int containerIndex)
|
void InstallUiController::refreshContainerStatus(const QString &serverId, int containerIndex)
|
||||||
@@ -379,13 +390,23 @@ void InstallUiController::refreshContainerStatus(const QString &serverId, int co
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using StatusResult = std::pair<int, int>; // {status, errorCode}
|
||||||
|
InstallController *installController = m_installController;
|
||||||
|
auto *watcher = new QFutureWatcher<StatusResult>(this);
|
||||||
|
QObject::connect(watcher, &QFutureWatcher<StatusResult>::finished, this, [this, watcher]() {
|
||||||
|
const StatusResult result = watcher->result();
|
||||||
|
watcher->deleteLater();
|
||||||
|
emit containerStatusRefreshed(result.first, result.second);
|
||||||
|
});
|
||||||
|
QFuture<StatusResult> future = QtConcurrent::run([installController, serverId, container]() -> StatusResult {
|
||||||
int status = 3;
|
int status = 3;
|
||||||
const ErrorCode errorCode = m_installController->queryDockerContainerStatus(serverId, container, status);
|
const ErrorCode errorCode = installController->queryDockerContainerStatus(serverId, container, status);
|
||||||
if (errorCode != ErrorCode::NoError) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
emit containerStatusRefreshed(3);
|
return { 3, static_cast<int>(errorCode) };
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
emit containerStatusRefreshed(status);
|
return { status, static_cast<int>(ErrorCode::NoError) };
|
||||||
|
});
|
||||||
|
watcher->setFuture(future);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstallUiController::refreshContainerDiagnostics(const QString &serverId, int containerIndex, int port)
|
void InstallUiController::refreshContainerDiagnostics(const QString &serverId, int containerIndex, int port)
|
||||||
@@ -395,14 +416,27 @@ void InstallUiController::refreshContainerDiagnostics(const QString &serverId, i
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
MtProxyContainerDiagnostics diag;
|
using DiagResult = std::pair<bool, MtProxyContainerDiagnostics>;
|
||||||
const ErrorCode errorCode = m_installController->queryMtProxyDiagnostics(serverId, container, port, diag);
|
InstallController *installController = m_installController;
|
||||||
if (errorCode != ErrorCode::NoError) {
|
auto *watcher = new QFutureWatcher<DiagResult>(this);
|
||||||
|
QObject::connect(watcher, &QFutureWatcher<DiagResult>::finished, this, [this, watcher]() {
|
||||||
|
const DiagResult result = watcher->result();
|
||||||
|
watcher->deleteLater();
|
||||||
|
if (!result.first) {
|
||||||
emit containerDiagnosticsRefreshed(false, false, -1, QString(), QString());
|
emit containerDiagnosticsRefreshed(false, false, -1, QString(), QString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const MtProxyContainerDiagnostics &diag = result.second;
|
||||||
emit containerDiagnosticsRefreshed(diag.portReachable, diag.upstreamReachable, diag.clientsConnected,
|
emit containerDiagnosticsRefreshed(diag.portReachable, diag.upstreamReachable, diag.clientsConnected,
|
||||||
diag.lastConfigRefresh, diag.statsEndpoint);
|
diag.lastConfigRefresh, diag.statsEndpoint);
|
||||||
|
});
|
||||||
|
QFuture<DiagResult> future =
|
||||||
|
QtConcurrent::run([installController, serverId, container, port]() -> DiagResult {
|
||||||
|
MtProxyContainerDiagnostics diag;
|
||||||
|
const ErrorCode errorCode = installController->queryMtProxyDiagnostics(serverId, container, port, diag);
|
||||||
|
return { errorCode == ErrorCode::NoError, diag };
|
||||||
|
});
|
||||||
|
watcher->setFuture(future);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstallUiController::fetchContainerSecret(const QString &serverId, int containerIndex)
|
void InstallUiController::fetchContainerSecret(const QString &serverId, int containerIndex)
|
||||||
@@ -412,8 +446,17 @@ void InstallUiController::fetchContainerSecret(const QString &serverId, int cont
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString secret = m_installController->fetchDockerContainerSecret(serverId, container);
|
InstallController *installController = m_installController;
|
||||||
|
auto *watcher = new QFutureWatcher<QString>(this);
|
||||||
|
QObject::connect(watcher, &QFutureWatcher<QString>::finished, this, [this, watcher]() {
|
||||||
|
const QString secret = watcher->result();
|
||||||
|
watcher->deleteLater();
|
||||||
emit containerSecretFetched(secret);
|
emit containerSecretFetched(secret);
|
||||||
|
});
|
||||||
|
QFuture<QString> future = QtConcurrent::run([installController, serverId, container]() -> QString {
|
||||||
|
return installController->fetchDockerContainerSecret(serverId, container);
|
||||||
|
});
|
||||||
|
watcher->setFuture(future);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InstallUiController::rebootServer(const QString &serverId)
|
void InstallUiController::rebootServer(const QString &serverId)
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ signals:
|
|||||||
void removeAllContainersFinished(const QString &finishedMessage);
|
void removeAllContainersFinished(const QString &finishedMessage);
|
||||||
void removeContainerFinished(const QString &finishedMessage);
|
void removeContainerFinished(const QString &finishedMessage);
|
||||||
void setContainerEnabledFinished(bool enabled);
|
void setContainerEnabledFinished(bool enabled);
|
||||||
void containerStatusRefreshed(int status);
|
void containerStatusRefreshed(int status, int errorCode);
|
||||||
void containerDiagnosticsRefreshed(bool portReachable, bool upstreamReachable, int clientsConnected,
|
void containerDiagnosticsRefreshed(bool portReachable, bool upstreamReachable, int clientsConnected,
|
||||||
const QString &lastConfigRefresh, const QString &statsEndpoint);
|
const QString &lastConfigRefresh, const QString &statsEndpoint);
|
||||||
void containerSecretFetched(const QString &secret);
|
void containerSecretFetched(const QString &secret);
|
||||||
|
|||||||
@@ -8,8 +8,6 @@
|
|||||||
#include "core/utils/constants/configKeys.h"
|
#include "core/utils/constants/configKeys.h"
|
||||||
#include "qrcodegen.hpp"
|
#include "qrcodegen.hpp"
|
||||||
|
|
||||||
#include <QClipboard>
|
|
||||||
#include <QGuiApplication>
|
|
||||||
#include <QHostAddress>
|
#include <QHostAddress>
|
||||||
#include <QRegExp>
|
#include <QRegExp>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
@@ -332,7 +330,7 @@ void MtProxyConfigModel::removeAdditionalSecret(int idx) {
|
|||||||
QVariantList MtProxyConfigModel::additionalSecretsList() const {
|
QVariantList MtProxyConfigModel::additionalSecretsList() const {
|
||||||
QVariantList out;
|
QVariantList out;
|
||||||
out.reserve(m_protocolConfig.additionalSecrets.size());
|
out.reserve(m_protocolConfig.additionalSecrets.size());
|
||||||
for (const auto &s: m_protocolConfig.additionalSecrets) {
|
for (const auto &s : m_protocolConfig.additionalSecrets) {
|
||||||
if (!s.isEmpty()) {
|
if (!s.isEmpty()) {
|
||||||
out.append(s);
|
out.append(s);
|
||||||
}
|
}
|
||||||
@@ -398,6 +396,9 @@ bool MtProxyConfigModel::isValidPublicHost(const QString &host) const {
|
|||||||
return NetworkUtilities::checkIPv4Format(t);
|
return NetworkUtilities::checkIPv4Format(t);
|
||||||
}
|
}
|
||||||
if (a.protocol() == QHostAddress::IPv6Protocol) {
|
if (a.protocol() == QHostAddress::IPv6Protocol) {
|
||||||
|
if (a.isNull() || a.isLoopback() || a == QHostAddress(QHostAddress::AnyIPv6)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
static const QRegularExpression onlyAsciiDigits(QStringLiteral(R"(^\d+$)"));
|
static const QRegularExpression onlyAsciiDigits(QStringLiteral(R"(^\d+$)"));
|
||||||
@@ -504,20 +505,12 @@ bool MtProxyConfigModel::isValidFakeTlsDomain(const QString &domain) const {
|
|||||||
if (!re.exactMatch(t)) {
|
if (!re.exactMatch(t)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// ee + 32 hex (base secret) + hex(UTF-8 domain); keep headroom under typical client limits.
|
|
||||||
if (t.toUtf8().size() > 111) {
|
if (t.toUtf8().size() > 111) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString MtProxyConfigModel::clipboardText() const {
|
|
||||||
if (QClipboard *c = QGuiApplication::clipboard()) {
|
|
||||||
return c->text();
|
|
||||||
}
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
|
|
||||||
QString MtProxyConfigModel::sanitizeFakeTlsDomainFieldText(const QString &input) const {
|
QString MtProxyConfigModel::sanitizeFakeTlsDomainFieldText(const QString &input) const {
|
||||||
const QString t = normalizeFakeTlsDomainInput(input);
|
const QString t = normalizeFakeTlsDomainInput(input);
|
||||||
QString out;
|
QString out;
|
||||||
@@ -578,7 +571,6 @@ QString MtProxyConfigModel::sanitizeMtProxyTagFieldText(const QString &input) co
|
|||||||
if (trimmed.startsWith(QLatin1String("0x"), Qt::CaseInsensitive)) {
|
if (trimmed.startsWith(QLatin1String("0x"), Qt::CaseInsensitive)) {
|
||||||
trimmed = trimmed.mid(2).trimmed();
|
trimmed = trimmed.mid(2).trimmed();
|
||||||
}
|
}
|
||||||
// Prefer a contiguous 32-hex run (paste from bot message with extra text).
|
|
||||||
static const QRegularExpression runHex(QStringLiteral(R"(([0-9a-fA-F]{32}))"));
|
static const QRegularExpression runHex(QStringLiteral(R"(([0-9a-fA-F]{32}))"));
|
||||||
const QRegularExpressionMatch m = runHex.match(trimmed);
|
const QRegularExpressionMatch m = runHex.match(trimmed);
|
||||||
if (m.hasMatch()) {
|
if (m.hasMatch()) {
|
||||||
@@ -599,18 +591,6 @@ QString MtProxyConfigModel::sanitizeMtProxyTagFieldText(const QString &input) co
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString MtProxyConfigModel::sanitizeWorkersFieldText(const QString &input) const {
|
|
||||||
QString out;
|
|
||||||
out.reserve(qMin(input.size(), 3));
|
|
||||||
for (const QChar &c: input) {
|
|
||||||
const ushort u = c.unicode();
|
|
||||||
if (u >= '0' && u <= '9' && out.size() < 3) {
|
|
||||||
out.append(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString MtProxyConfigModel::sanitizeOptionalIpv4FieldText(const QString &input) const {
|
QString MtProxyConfigModel::sanitizeOptionalIpv4FieldText(const QString &input) const {
|
||||||
QString out;
|
QString out;
|
||||||
out.reserve(qMin(input.size(), 15));
|
out.reserve(qMin(input.size(), 15));
|
||||||
|
|||||||
@@ -128,16 +128,12 @@ public slots:
|
|||||||
|
|
||||||
Q_INVOKABLE bool isFakeTlsDomainInputAllowed(const QString &text) const;
|
Q_INVOKABLE bool isFakeTlsDomainInputAllowed(const QString &text) const;
|
||||||
|
|
||||||
Q_INVOKABLE QString clipboardText() const;
|
|
||||||
|
|
||||||
Q_INVOKABLE QString sanitizePublicHostFieldText(const QString &input) const;
|
Q_INVOKABLE QString sanitizePublicHostFieldText(const QString &input) const;
|
||||||
|
|
||||||
Q_INVOKABLE QString sanitizePortFieldText(const QString &input) const;
|
Q_INVOKABLE QString sanitizePortFieldText(const QString &input) const;
|
||||||
|
|
||||||
Q_INVOKABLE QString sanitizeMtProxyTagFieldText(const QString &input) const;
|
Q_INVOKABLE QString sanitizeMtProxyTagFieldText(const QString &input) const;
|
||||||
|
|
||||||
Q_INVOKABLE QString sanitizeWorkersFieldText(const QString &input) const;
|
|
||||||
|
|
||||||
Q_INVOKABLE QString sanitizeOptionalIpv4FieldText(const QString &input) const;
|
Q_INVOKABLE QString sanitizeOptionalIpv4FieldText(const QString &input) const;
|
||||||
|
|
||||||
Q_INVOKABLE bool isFakeTlsDomainTypingIncomplete(const QString &text) const;
|
Q_INVOKABLE bool isFakeTlsDomainTypingIncomplete(const QString &text) const;
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
#include "telemtConfigModel.h"
|
#include "telemtConfigModel.h"
|
||||||
|
|
||||||
#include <QRegularExpression>
|
#include "ui/models/utils/mtproxy_public_host_input.h"
|
||||||
|
|
||||||
|
#include <QHostAddress>
|
||||||
|
#include <QRegExp>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
#include <qqml.h>
|
||||||
|
|
||||||
|
#include "core/utils/networkUtilities.h"
|
||||||
#include "core/utils/qrCodeUtils.h"
|
#include "core/utils/qrCodeUtils.h"
|
||||||
#include "core/utils/constants/configKeys.h"
|
#include "core/utils/constants/configKeys.h"
|
||||||
#include "core/utils/constants/protocolConstants.h"
|
#include "core/utils/constants/protocolConstants.h"
|
||||||
@@ -9,7 +15,9 @@
|
|||||||
|
|
||||||
using namespace amnezia;
|
using namespace amnezia;
|
||||||
|
|
||||||
TelemtConfigModel::TelemtConfigModel(QObject *parent) : QAbstractListModel(parent) {}
|
TelemtConfigModel::TelemtConfigModel(QObject *parent) : QAbstractListModel(parent) {
|
||||||
|
qmlRegisterType<PublicHostInputValidator>("TelemtConfig", 1, 0, "PublicHostInputValidator");
|
||||||
|
}
|
||||||
|
|
||||||
void TelemtConfigModel::applyDefaults(TelemtProtocolConfig &c) {
|
void TelemtConfigModel::applyDefaults(TelemtProtocolConfig &c) {
|
||||||
if (c.port.isEmpty()) {
|
if (c.port.isEmpty()) {
|
||||||
@@ -49,7 +57,11 @@ bool TelemtConfigModel::setData(const QModelIndex &index, const QVariant &value,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Roles::TagRole: {
|
case Roles::TagRole: {
|
||||||
m_protocolConfig.tag = value.toString();
|
const QString tag = sanitizeMtProxyTagFieldText(value.toString());
|
||||||
|
if (!isValidMtProxyTag(tag)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_protocolConfig.tag = tag;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Roles::IsEnabledRole: {
|
case Roles::IsEnabledRole: {
|
||||||
@@ -57,7 +69,11 @@ bool TelemtConfigModel::setData(const QModelIndex &index, const QVariant &value,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Roles::PublicHostRole: {
|
case Roles::PublicHostRole: {
|
||||||
m_protocolConfig.publicHost = value.toString();
|
const QString h = value.toString().trimmed();
|
||||||
|
if (!isValidPublicHost(h)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_protocolConfig.publicHost = h;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Roles::TransportModeRole: {
|
case Roles::TransportModeRole: {
|
||||||
@@ -65,7 +81,11 @@ bool TelemtConfigModel::setData(const QModelIndex &index, const QVariant &value,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Roles::TlsDomainRole: {
|
case Roles::TlsDomainRole: {
|
||||||
m_protocolConfig.tlsDomain = value.toString();
|
const QString d = value.toString().trimmed();
|
||||||
|
if (!isValidFakeTlsDomain(d)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_protocolConfig.tlsDomain = d;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Roles::AdditionalSecretsRole: {
|
case Roles::AdditionalSecretsRole: {
|
||||||
@@ -85,11 +105,19 @@ bool TelemtConfigModel::setData(const QModelIndex &index, const QVariant &value,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Roles::NatInternalIpRole: {
|
case Roles::NatInternalIpRole: {
|
||||||
m_protocolConfig.natInternalIp = value.toString();
|
const QString ip = value.toString().trimmed();
|
||||||
|
if (!isValidOptionalIpv4(ip)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_protocolConfig.natInternalIp = ip;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Roles::NatExternalIpRole: {
|
case Roles::NatExternalIpRole: {
|
||||||
m_protocolConfig.natExternalIp = value.toString();
|
const QString ip = value.toString().trimmed();
|
||||||
|
if (!isValidOptionalIpv4(ip)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_protocolConfig.natExternalIp = ip;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Roles::MaskEnabledRole: {
|
case Roles::MaskEnabledRole: {
|
||||||
@@ -238,7 +266,7 @@ void TelemtConfigModel::setSecret(const QString &secret) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool TelemtConfigModel::validateAndSetSecret(const QString &rawSecret) {
|
bool TelemtConfigModel::validateAndSetSecret(const QString &rawSecret) {
|
||||||
if (!QRegularExpression(QStringLiteral("^[0-9a-fA-F]{32}$")).match(rawSecret).hasMatch()) {
|
if (!QRegularExpression("^[0-9a-fA-F]{32}$").match(rawSecret).hasMatch()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
setData(index(0), rawSecret, SecretRole);
|
setData(index(0), rawSecret, SecretRole);
|
||||||
@@ -254,7 +282,11 @@ void TelemtConfigModel::setTag(const QString &tag) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TelemtConfigModel::setPublicHost(const QString &host) {
|
void TelemtConfigModel::setPublicHost(const QString &host) {
|
||||||
setData(index(0), host, PublicHostRole);
|
const QString t = host.trimmed();
|
||||||
|
if (!isValidPublicHost(t)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setData(index(0), t, PublicHostRole);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TelemtConfigModel::setTransportMode(const QString &mode) {
|
void TelemtConfigModel::setTransportMode(const QString &mode) {
|
||||||
@@ -262,12 +294,14 @@ void TelemtConfigModel::setTransportMode(const QString &mode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QString TelemtConfigModel::getTransportMode() const {
|
QString TelemtConfigModel::getTransportMode() const {
|
||||||
return m_protocolConfig.transportMode.isEmpty() ? QString::fromUtf8(protocols::telemt::transportModeStandard)
|
return m_protocolConfig.transportMode.isEmpty()
|
||||||
|
? QString(protocols::telemt::transportModeStandard)
|
||||||
: m_protocolConfig.transportMode;
|
: m_protocolConfig.transportMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString TelemtConfigModel::getTlsDomain() const {
|
QString TelemtConfigModel::getTlsDomain() const {
|
||||||
return m_protocolConfig.tlsDomain.isEmpty() ? QString::fromUtf8(protocols::telemt::defaultTlsDomain)
|
return m_protocolConfig.tlsDomain.isEmpty()
|
||||||
|
? QString(protocols::telemt::defaultTlsDomain)
|
||||||
: m_protocolConfig.tlsDomain;
|
: m_protocolConfig.tlsDomain;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,7 +310,11 @@ QString TelemtConfigModel::getPublicHost() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TelemtConfigModel::setTlsDomain(const QString &domain) {
|
void TelemtConfigModel::setTlsDomain(const QString &domain) {
|
||||||
setData(index(0), domain, TlsDomainRole);
|
const QString t = domain.trimmed();
|
||||||
|
if (!isValidFakeTlsDomain(t)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setData(index(0), t, TlsDomainRole);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TelemtConfigModel::setWorkersMode(const QString &mode) {
|
void TelemtConfigModel::setWorkersMode(const QString &mode) {
|
||||||
@@ -292,11 +330,19 @@ void TelemtConfigModel::setNatEnabled(bool enabled) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TelemtConfigModel::setNatInternalIp(const QString &ip) {
|
void TelemtConfigModel::setNatInternalIp(const QString &ip) {
|
||||||
setData(index(0), ip, NatInternalIpRole);
|
const QString t = ip.trimmed();
|
||||||
|
if (!isValidOptionalIpv4(t)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setData(index(0), t, NatInternalIpRole);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TelemtConfigModel::setNatExternalIp(const QString &ip) {
|
void TelemtConfigModel::setNatExternalIp(const QString &ip) {
|
||||||
setData(index(0), ip, NatExternalIpRole);
|
const QString t = ip.trimmed();
|
||||||
|
if (!isValidOptionalIpv4(t)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setData(index(0), t, NatExternalIpRole);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TelemtConfigModel::setMaskEnabled(bool enabled) {
|
void TelemtConfigModel::setMaskEnabled(bool enabled) {
|
||||||
@@ -334,6 +380,17 @@ void TelemtConfigModel::removeAdditionalSecret(int idx) {
|
|||||||
emit dataChanged(index(0), index(0), QList<int>{AdditionalSecretsRole});
|
emit dataChanged(index(0), index(0), QList<int>{AdditionalSecretsRole});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QVariantList TelemtConfigModel::additionalSecretsList() const {
|
||||||
|
QVariantList out;
|
||||||
|
out.reserve(m_protocolConfig.additionalSecrets.size());
|
||||||
|
for (const auto &s : m_protocolConfig.additionalSecrets) {
|
||||||
|
if (!s.isEmpty()) {
|
||||||
|
out.append(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
void TelemtConfigModel::setEnabled(bool enabled) {
|
void TelemtConfigModel::setEnabled(bool enabled) {
|
||||||
m_protocolConfig.isEnabled = enabled;
|
m_protocolConfig.isEnabled = enabled;
|
||||||
emit dataChanged(index(0), index(0), QList<int>{IsEnabledRole});
|
emit dataChanged(index(0), index(0), QList<int>{IsEnabledRole});
|
||||||
@@ -348,15 +405,15 @@ QString TelemtConfigModel::generateQrCode(const QString &text) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QString TelemtConfigModel::defaultTlsDomain() const {
|
QString TelemtConfigModel::defaultTlsDomain() const {
|
||||||
return QString::fromUtf8(protocols::telemt::defaultTlsDomain);
|
return protocols::telemt::defaultTlsDomain;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString TelemtConfigModel::defaultPort() const {
|
QString TelemtConfigModel::defaultPort() const {
|
||||||
return QString::fromUtf8(protocols::telemt::defaultPort);
|
return protocols::telemt::defaultPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString TelemtConfigModel::defaultWorkers() const {
|
QString TelemtConfigModel::defaultWorkers() const {
|
||||||
return QString::fromUtf8(protocols::telemt::defaultWorkers);
|
return protocols::telemt::defaultWorkers;
|
||||||
}
|
}
|
||||||
|
|
||||||
int TelemtConfigModel::maxWorkers() const {
|
int TelemtConfigModel::maxWorkers() const {
|
||||||
@@ -364,19 +421,303 @@ int TelemtConfigModel::maxWorkers() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QString TelemtConfigModel::transportModeStandard() const {
|
QString TelemtConfigModel::transportModeStandard() const {
|
||||||
return QString::fromUtf8(protocols::telemt::transportModeStandard);
|
return protocols::telemt::transportModeStandard;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString TelemtConfigModel::transportModeFakeTLS() const {
|
QString TelemtConfigModel::transportModeFakeTLS() const {
|
||||||
return QString::fromUtf8(protocols::telemt::transportModeFakeTLS);
|
return protocols::telemt::transportModeFakeTLS;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString TelemtConfigModel::workersModeAuto() const {
|
QString TelemtConfigModel::workersModeAuto() const {
|
||||||
return QString::fromUtf8(protocols::telemt::workersModeAuto);
|
return protocols::telemt::workersModeAuto;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString TelemtConfigModel::workersModeManual() const {
|
QString TelemtConfigModel::workersModeManual() const {
|
||||||
return QString::fromUtf8(protocols::telemt::workersModeManual);
|
return protocols::telemt::workersModeManual;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TelemtConfigModel::isValidPublicHost(const QString &host) const {
|
||||||
|
const QString t = host.trimmed();
|
||||||
|
if (t.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (t.length() > 253) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QHostAddress a(t);
|
||||||
|
if (a.protocol() == QHostAddress::IPv4Protocol) {
|
||||||
|
return NetworkUtilities::checkIPv4Format(t);
|
||||||
|
}
|
||||||
|
if (a.protocol() == QHostAddress::IPv6Protocol) {
|
||||||
|
if (a.isNull() || a.isLoopback() || a == QHostAddress(QHostAddress::AnyIPv6)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
static const QRegularExpression onlyAsciiDigits(QStringLiteral(R"(^\d+$)"));
|
||||||
|
if (onlyAsciiDigits.match(t).hasMatch()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return NetworkUtilities::domainRegExp().exactMatch(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TelemtConfigModel::isPublicHostInputAllowed(const QString &text) const {
|
||||||
|
return mtproxyPublicHostInputAllowed(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TelemtConfigModel::isPublicHostTypingIncomplete(const QString &text) const {
|
||||||
|
const QString t = text.trimmed();
|
||||||
|
if (isValidPublicHost(t)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const QRegularExpression onlyDigitDot(QStringLiteral(R"(^[0-9.]+$)"));
|
||||||
|
if (onlyDigitDot.match(t).hasMatch()) {
|
||||||
|
if (t.endsWith(QLatin1Char('.'))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const QStringList parts = t.split(QLatin1Char('.'), Qt::KeepEmptyParts);
|
||||||
|
if (parts.size() < 4) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
for (const QString &part: parts) {
|
||||||
|
if (part.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t.contains(QLatin1Char(':'))) {
|
||||||
|
if (t.contains(QLatin1String(":::"))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (t.endsWith(QLatin1Char(':'))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
QHostAddress a(t);
|
||||||
|
if (a.protocol() == QHostAddress::IPv6Protocol) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!t.contains(QLatin1String("::")) && t.count(QLatin1Char(':')) < 7 && !t.contains(QLatin1Char('.'))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!t.contains(QLatin1Char('.'))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TelemtConfigModel::isValidMtProxyTag(const QString &tag) const {
|
||||||
|
if (tag.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
static const QRegularExpression re(
|
||||||
|
QStringLiteral("^([0-9a-fA-F]{%1})$").arg(protocols::telemt::botTagHexLength));
|
||||||
|
return re.match(tag).hasMatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TelemtConfigModel::isMtProxyTagTypingIncomplete(const QString &text) const {
|
||||||
|
const QString t = text.trimmed();
|
||||||
|
if (t.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
static const QRegularExpression hexOnly(QStringLiteral(R"(^[0-9a-fA-F]*$)"));
|
||||||
|
if (!hexOnly.match(t).hasMatch()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return t.size() < protocols::telemt::botTagHexLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
int TelemtConfigModel::mtProxyBotTagHexLength() const {
|
||||||
|
return protocols::telemt::botTagHexLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TelemtConfigModel::isValidFakeTlsDomain(const QString &domain) const {
|
||||||
|
const QString t = domain.trimmed();
|
||||||
|
if (t.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (t.length() > 253) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QHostAddress addr;
|
||||||
|
if (addr.setAddress(t)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
static const QRegularExpression onlyAsciiDigits(QStringLiteral(R"(^\d+$)"));
|
||||||
|
if (onlyAsciiDigits.match(t).hasMatch()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QRegExp re(NetworkUtilities::domainRegExp());
|
||||||
|
re.setCaseSensitivity(Qt::CaseInsensitive);
|
||||||
|
if (!re.exactMatch(t)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (t.toUtf8().size() > 111) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString TelemtConfigModel::normalizeFakeTlsDomainInput(const QString &input) const {
|
||||||
|
QString t = input.trimmed();
|
||||||
|
if (t.startsWith(QLatin1String("https://"), Qt::CaseInsensitive)) {
|
||||||
|
t = t.mid(8);
|
||||||
|
} else if (t.startsWith(QLatin1String("http://"), Qt::CaseInsensitive)) {
|
||||||
|
t = t.mid(7);
|
||||||
|
}
|
||||||
|
if (const int slash = t.indexOf(QLatin1Char('/')); slash >= 0) {
|
||||||
|
t = t.left(slash);
|
||||||
|
}
|
||||||
|
if (const int at = t.indexOf(QLatin1Char('@')); at >= 0) {
|
||||||
|
t = t.mid(at + 1);
|
||||||
|
}
|
||||||
|
if (const int colon = t.indexOf(QLatin1Char(':')); colon >= 0) {
|
||||||
|
t = t.left(colon);
|
||||||
|
}
|
||||||
|
if (t.startsWith(QLatin1String("www."), Qt::CaseInsensitive)) {
|
||||||
|
const QString rest = t.mid(4);
|
||||||
|
if (rest.contains(QLatin1Char('.'))) {
|
||||||
|
t = rest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return t.trimmed();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TelemtConfigModel::isFakeTlsDomainTypingIncomplete(const QString &text) const {
|
||||||
|
const QString t = text.trimmed();
|
||||||
|
if (t.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (isValidFakeTlsDomain(t)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (t.contains(QLatin1Char('/')) || t.contains(QLatin1Char(':')) || t.contains(QLatin1Char('@'))
|
||||||
|
|| t.contains(QLatin1Char(' '))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (t.contains(QLatin1String(".."))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!t.contains(QLatin1Char('.'))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (t.endsWith(QLatin1Char('.'))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
static const QRegularExpression legalPartial(QStringLiteral(R"(^[a-zA-Z0-9.-]*$)"));
|
||||||
|
if (!legalPartial.match(t).hasMatch()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TelemtConfigModel::isFakeTlsDomainInputAllowed(const QString &text) const {
|
||||||
|
if (text.length() > 253) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
static const QRegularExpression re(QStringLiteral(R"(^[a-zA-Z0-9.-]*$)"));
|
||||||
|
return re.match(text).hasMatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString TelemtConfigModel::sanitizeFakeTlsDomainFieldText(const QString &input) const {
|
||||||
|
const QString t = normalizeFakeTlsDomainInput(input);
|
||||||
|
QString out;
|
||||||
|
out.reserve(t.size());
|
||||||
|
for (const QChar &c: t) {
|
||||||
|
const ushort u = c.unicode();
|
||||||
|
const bool letter = (u >= 'a' && u <= 'z') || (u >= 'A' && u <= 'Z');
|
||||||
|
const bool digit = (u >= '0' && u <= '9');
|
||||||
|
if (letter || digit || u == '.' || u == '-') {
|
||||||
|
out.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (out.size() > 253) {
|
||||||
|
out.truncate(253);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString TelemtConfigModel::sanitizePublicHostFieldText(const QString &input) const {
|
||||||
|
QString out;
|
||||||
|
const int cap = qMin(input.size(), 253);
|
||||||
|
out.reserve(cap);
|
||||||
|
for (const QChar &c: input) {
|
||||||
|
if (out.size() >= 253) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const ushort u = c.unicode();
|
||||||
|
if ((u >= 'a' && u <= 'z') || (u >= 'A' && u <= 'Z') || (u >= '0' && u <= '9') || u == '.' || u == ':' ||
|
||||||
|
u == '-') {
|
||||||
|
out.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString TelemtConfigModel::sanitizePortFieldText(const QString &input) const {
|
||||||
|
QString out;
|
||||||
|
out.reserve(qMin(input.size(), 5));
|
||||||
|
for (const QChar &c: input) {
|
||||||
|
const ushort u = c.unicode();
|
||||||
|
if (u >= '0' && u <= '9' && out.size() < 5) {
|
||||||
|
out.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString TelemtConfigModel::sanitizeMtProxyTagFieldText(const QString &input) const {
|
||||||
|
QString trimmed = input.trimmed();
|
||||||
|
if (trimmed.startsWith(QLatin1String("0x"), Qt::CaseInsensitive)) {
|
||||||
|
trimmed = trimmed.mid(2).trimmed();
|
||||||
|
}
|
||||||
|
static const QRegularExpression runHex(QStringLiteral(R"(([0-9a-fA-F]{32}))"));
|
||||||
|
const QRegularExpressionMatch m = runHex.match(trimmed);
|
||||||
|
if (m.hasMatch()) {
|
||||||
|
return m.captured(1);
|
||||||
|
}
|
||||||
|
const int cap = protocols::telemt::botTagHexLength;
|
||||||
|
QString out;
|
||||||
|
out.reserve(qMin(trimmed.size(), cap));
|
||||||
|
for (const QChar &c: trimmed) {
|
||||||
|
if (out.size() >= cap) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const ushort u = c.unicode();
|
||||||
|
if ((u >= '0' && u <= '9') || (u >= 'a' && u <= 'f') || (u >= 'A' && u <= 'F')) {
|
||||||
|
out.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString TelemtConfigModel::sanitizeOptionalIpv4FieldText(const QString &input) const {
|
||||||
|
QString out;
|
||||||
|
out.reserve(qMin(input.size(), 15));
|
||||||
|
for (const QChar &c: input) {
|
||||||
|
if (out.size() >= 15) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const ushort u = c.unicode();
|
||||||
|
if ((u >= '0' && u <= '9') || u == '.') {
|
||||||
|
out.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TelemtConfigModel::isValidOptionalIpv4(const QString &ip) const {
|
||||||
|
const QString t = ip.trimmed();
|
||||||
|
if (t.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return NetworkUtilities::checkIPv4Format(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
QHash<int, QByteArray> TelemtConfigModel::roleNames() const {
|
QHash<int, QByteArray> TelemtConfigModel::roleNames() const {
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ public slots:
|
|||||||
|
|
||||||
Q_INVOKABLE void removeAdditionalSecret(int idx);
|
Q_INVOKABLE void removeAdditionalSecret(int idx);
|
||||||
|
|
||||||
|
Q_INVOKABLE QVariantList additionalSecretsList() const;
|
||||||
|
|
||||||
Q_INVOKABLE QString generateQrCode(const QString &text);
|
Q_INVOKABLE QString generateQrCode(const QString &text);
|
||||||
|
|
||||||
Q_INVOKABLE void setEnabled(bool enabled);
|
Q_INVOKABLE void setEnabled(bool enabled);
|
||||||
@@ -116,12 +118,44 @@ public slots:
|
|||||||
|
|
||||||
Q_INVOKABLE QString workersModeManual() const;
|
Q_INVOKABLE QString workersModeManual() const;
|
||||||
|
|
||||||
|
Q_INVOKABLE bool isValidPublicHost(const QString &host) const;
|
||||||
|
|
||||||
|
Q_INVOKABLE bool isPublicHostInputAllowed(const QString &text) const;
|
||||||
|
|
||||||
|
Q_INVOKABLE bool isPublicHostTypingIncomplete(const QString &text) const;
|
||||||
|
|
||||||
|
Q_INVOKABLE bool isValidMtProxyTag(const QString &tag) const;
|
||||||
|
|
||||||
|
Q_INVOKABLE bool isMtProxyTagTypingIncomplete(const QString &text) const;
|
||||||
|
|
||||||
|
Q_INVOKABLE int mtProxyBotTagHexLength() const;
|
||||||
|
|
||||||
|
Q_INVOKABLE bool isValidFakeTlsDomain(const QString &domain) const;
|
||||||
|
|
||||||
|
Q_INVOKABLE bool isFakeTlsDomainTypingIncomplete(const QString &text) const;
|
||||||
|
|
||||||
|
Q_INVOKABLE bool isFakeTlsDomainInputAllowed(const QString &text) const;
|
||||||
|
|
||||||
|
Q_INVOKABLE QString sanitizeFakeTlsDomainFieldText(const QString &input) const;
|
||||||
|
|
||||||
|
Q_INVOKABLE QString sanitizePublicHostFieldText(const QString &input) const;
|
||||||
|
|
||||||
|
Q_INVOKABLE QString sanitizePortFieldText(const QString &input) const;
|
||||||
|
|
||||||
|
Q_INVOKABLE QString sanitizeMtProxyTagFieldText(const QString &input) const;
|
||||||
|
|
||||||
|
Q_INVOKABLE QString sanitizeOptionalIpv4FieldText(const QString &input) const;
|
||||||
|
|
||||||
|
Q_INVOKABLE bool isValidOptionalIpv4(const QString &ip) const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void applyDefaults(amnezia::TelemtProtocolConfig &c);
|
static void applyDefaults(amnezia::TelemtProtocolConfig &c);
|
||||||
|
|
||||||
|
QString normalizeFakeTlsDomainInput(const QString &input) const;
|
||||||
|
|
||||||
amnezia::DockerContainer m_container = amnezia::DockerContainer::None;
|
amnezia::DockerContainer m_container = amnezia::DockerContainer::None;
|
||||||
QJsonObject m_fullConfig;
|
QJsonObject m_fullConfig;
|
||||||
amnezia::TelemtProtocolConfig m_protocolConfig;
|
amnezia::TelemtProtocolConfig m_protocolConfig;
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ import "../Controls2/TextTypes"
|
|||||||
import "../Config"
|
import "../Config"
|
||||||
import "../Components"
|
import "../Components"
|
||||||
|
|
||||||
|
|
||||||
PageType {
|
PageType {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property int containerStatus: 1
|
property int containerStatus: 1
|
||||||
|
property int statusErrorCode: 0
|
||||||
property bool isUpdating: false
|
property bool isUpdating: false
|
||||||
property bool isCheckingStatus: false
|
property bool isCheckingStatus: false
|
||||||
property bool isFetchingSecret: false
|
property bool isFetchingSecret: false
|
||||||
@@ -261,6 +261,7 @@ PageType {
|
|||||||
isCheckingStatus = false
|
isCheckingStatus = false
|
||||||
isFetchingSecret = false
|
isFetchingSecret = false
|
||||||
busyIndicatorShown = false
|
busyIndicatorShown = false
|
||||||
|
statusErrorCode = 0
|
||||||
PageController.disableControls(false)
|
PageController.disableControls(false)
|
||||||
PageController.showBusyIndicator(false)
|
PageController.showBusyIndicator(false)
|
||||||
diagLoading = false
|
diagLoading = false
|
||||||
@@ -348,13 +349,18 @@ PageType {
|
|||||||
enabled ? qsTr("MTProxy started") : qsTr("MTProxy stopped"))
|
enabled ? qsTr("MTProxy started") : qsTr("MTProxy stopped"))
|
||||||
}
|
}
|
||||||
|
|
||||||
function onContainerStatusRefreshed(status) {
|
function onContainerStatusRefreshed(status, errorCode) {
|
||||||
if (!root.visible) {
|
if (!root.visible) {
|
||||||
isCheckingStatus = false
|
isCheckingStatus = false
|
||||||
isFetchingSecret = false
|
isFetchingSecret = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
containerStatus = status
|
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.savedTransportMode = MtProxyConfigModel.getTransportMode()
|
||||||
root.savedTlsDomain = MtProxyConfigModel.getTlsDomain()
|
root.savedTlsDomain = MtProxyConfigModel.getTlsDomain()
|
||||||
@@ -842,6 +848,8 @@ PageType {
|
|||||||
width: settingsListView.width
|
width: settingsListView.width
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
|
readonly property bool fieldsEditable: isEnabled && containerStatus === 1 && !root.pageBusy
|
||||||
|
|
||||||
function mtProxyActiveSecretForBaseHex(baseHex) {
|
function mtProxyActiveSecretForBaseHex(baseHex) {
|
||||||
return root.mtProxyClientSecretForTabIndex(baseHex, root.syncedSecretTabIndex,
|
return root.mtProxyClientSecretForTabIndex(baseHex, root.syncedSecretTabIndex,
|
||||||
root.savedTlsDomain, MtProxyConfigModel.defaultTlsDomain())
|
root.savedTlsDomain, MtProxyConfigModel.defaultTlsDomain())
|
||||||
@@ -859,6 +867,25 @@ PageType {
|
|||||||
return "tg://proxy?server=" + mtProxyEffectiveHostForLinks() + "&port=" + port + "&secret=" + mtProxyActiveSecretForBaseHex(baseHex)
|
return "tg://proxy?server=" + mtProxyEffectiveHostForLinks() + "&port=" + port + "&secret=" + mtProxyActiveSecretForBaseHex(baseHex)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mtProxyIsAdditionalPersisted(hex) {
|
||||||
|
return root.mtProxyIsPersistedAdditionalHex(hex)
|
||||||
|
}
|
||||||
|
|
||||||
|
function mtProxyCopyText(text) {
|
||||||
|
GC.copyToClipBoard(text)
|
||||||
|
PageController.showNotificationMessage(qsTr("Copied"))
|
||||||
|
}
|
||||||
|
|
||||||
|
function mtProxyShareQr(link) {
|
||||||
|
ExportController.generateQrFromString(link)
|
||||||
|
PageController.goToShareConnectionPage(qsTr("Telegram connection link"),
|
||||||
|
qsTr("MTProxy connection link"), "", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
function mtProxyRemoveAdditionalSecret(idx) {
|
||||||
|
MtProxyConfigModel.removeAdditionalSecret(idx)
|
||||||
|
}
|
||||||
|
|
||||||
SwitcherType {
|
SwitcherType {
|
||||||
id: enableMtProxySwitch
|
id: enableMtProxySwitch
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@@ -887,6 +914,21 @@ PageType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
ColumnLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
@@ -921,6 +963,7 @@ PageType {
|
|||||||
image: "qrc:/images/controls/refresh-cw.svg"
|
image: "qrc:/images/controls/refresh-cw.svg"
|
||||||
imageColor: AmneziaStyle.color.paleGray
|
imageColor: AmneziaStyle.color.paleGray
|
||||||
visible: ServersUiController.isProcessedServerHasWriteAccess()
|
visible: ServersUiController.isProcessedServerHasWriteAccess()
|
||||||
|
enabled: fieldsEditable
|
||||||
onClicked: {
|
onClicked: {
|
||||||
var secretSnapshot = secret
|
var secretSnapshot = secret
|
||||||
showQuestionDrawer(
|
showQuestionDrawer(
|
||||||
@@ -949,6 +992,7 @@ PageType {
|
|||||||
|
|
||||||
TextFieldWithHeaderType {
|
TextFieldWithHeaderType {
|
||||||
id: publicHostTextField
|
id: publicHostTextField
|
||||||
|
enabled: fieldsEditable
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
@@ -1010,6 +1054,7 @@ PageType {
|
|||||||
|
|
||||||
TextFieldWithHeaderType {
|
TextFieldWithHeaderType {
|
||||||
id: portTextField
|
id: portTextField
|
||||||
|
enabled: fieldsEditable
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
@@ -1017,6 +1062,7 @@ PageType {
|
|||||||
headerText: qsTr("Server port")
|
headerText: qsTr("Server port")
|
||||||
textField.placeholderText: MtProxyConfigModel.defaultPort()
|
textField.placeholderText: MtProxyConfigModel.defaultPort()
|
||||||
textField.maximumLength: 5
|
textField.maximumLength: 5
|
||||||
|
textField.inputMethodHints: Qt.ImhDigitsOnly
|
||||||
textField.validator: IntValidator {
|
textField.validator: IntValidator {
|
||||||
bottom: 1
|
bottom: 1
|
||||||
top: 65535
|
top: 65535
|
||||||
@@ -1025,8 +1071,16 @@ PageType {
|
|||||||
var savedPort = port
|
var savedPort = port
|
||||||
textField.text = (savedPort === MtProxyConfigModel.defaultPort()) ? "" : savedPort
|
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.onEditingFinished: {
|
||||||
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
|
textField.text = MtProxyConfigModel.sanitizePortFieldText(textField.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1055,11 +1109,12 @@ PageType {
|
|||||||
|
|
||||||
TextFieldWithHeaderType {
|
TextFieldWithHeaderType {
|
||||||
id: tagTextField
|
id: tagTextField
|
||||||
|
enabled: fieldsEditable
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.bottomMargin: 16
|
Layout.bottomMargin: 16
|
||||||
headerText: qsTr("MTProxy bot tag (optional)")
|
headerText: qsTr("Promoted channel tag (optional)")
|
||||||
textField.placeholderText: qsTr("32 hex chars from @MTProxyBot (e.g. 3b7b2fa9…)")
|
textField.placeholderText: qsTr("32 hex chars from @MTProxyBot (e.g. 3b7b2fa9…)")
|
||||||
textField.text: tag
|
textField.text: tag
|
||||||
textField.maximumLength: MtProxyConfigModel.mtProxyBotTagHexLength()
|
textField.maximumLength: MtProxyConfigModel.mtProxyBotTagHexLength()
|
||||||
@@ -1138,6 +1193,7 @@ PageType {
|
|||||||
|
|
||||||
DropDownType {
|
DropDownType {
|
||||||
id: transportModeDropDown
|
id: transportModeDropDown
|
||||||
|
enabled: fieldsEditable
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
@@ -1173,6 +1229,7 @@ PageType {
|
|||||||
|
|
||||||
TextFieldWithHeaderType {
|
TextFieldWithHeaderType {
|
||||||
id: tlsDomainTextField
|
id: tlsDomainTextField
|
||||||
|
enabled: fieldsEditable
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
@@ -1180,10 +1237,22 @@ PageType {
|
|||||||
visible: transportMode === "faketls"
|
visible: transportMode === "faketls"
|
||||||
headerText: qsTr("FakeTLS domain")
|
headerText: qsTr("FakeTLS domain")
|
||||||
textField.placeholderText: root.previousTlsDomain
|
textField.placeholderText: root.previousTlsDomain
|
||||||
|
textField.validator: RegularExpressionValidator {
|
||||||
|
regularExpression: /^[A-Za-z0-9.-]*$/
|
||||||
|
}
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
var savedDomain = tlsDomain
|
var savedDomain = tlsDomain
|
||||||
textField.text = (savedDomain === MtProxyConfigModel.defaultTlsDomain() || savedDomain === "") ? "" : savedDomain
|
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.onEditingFinished: {
|
||||||
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
|
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
|
||||||
var domainValue = textField.text === "" ? MtProxyConfigModel.defaultTlsDomain() : textField.text
|
var domainValue = textField.text === "" ? MtProxyConfigModel.defaultTlsDomain() : textField.text
|
||||||
@@ -1243,6 +1312,7 @@ PageType {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: 0
|
spacing: 0
|
||||||
visible: advancedHeader.expanded
|
visible: advancedHeader.expanded
|
||||||
|
enabled: fieldsEditable
|
||||||
|
|
||||||
CaptionTextType {
|
CaptionTextType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@@ -1269,7 +1339,7 @@ PageType {
|
|||||||
delegate: ColumnLayout {
|
delegate: ColumnLayout {
|
||||||
id: addSecretDelegate
|
id: addSecretDelegate
|
||||||
property bool linksExpanded: false
|
property bool linksExpanded: false
|
||||||
readonly property bool linksPanelAllowed: root.mtProxyIsPersistedAdditionalHex(modelData)
|
readonly property bool linksPanelAllowed: settingsRoot.mtProxyIsAdditionalPersisted(modelData)
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
@@ -1312,7 +1382,7 @@ PageType {
|
|||||||
CaptionTextType {
|
CaptionTextType {
|
||||||
id: hexCaption
|
id: hexCaption
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: modelData
|
text: settingsRoot.mtProxyActiveSecretForBaseHex(modelData)
|
||||||
color: AmneziaStyle.color.paleGray
|
color: AmneziaStyle.color.paleGray
|
||||||
elide: Text.ElideMiddle
|
elide: Text.ElideMiddle
|
||||||
font.pixelSize: 13
|
font.pixelSize: 13
|
||||||
@@ -1347,15 +1417,9 @@ PageType {
|
|||||||
implicitWidth: 32
|
implicitWidth: 32
|
||||||
implicitHeight: 32
|
implicitHeight: 32
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
visible: ServersUiController.isProcessedServerHasWriteAccess()
|
|
||||||
image: "qrc:/images/controls/trash.svg"
|
image: "qrc:/images/controls/trash.svg"
|
||||||
imageColor: AmneziaStyle.color.vibrantRed
|
imageColor: AmneziaStyle.color.vibrantRed
|
||||||
onClicked: {
|
onClicked: settingsRoot.mtProxyRemoveAdditionalSecret(index)
|
||||||
MtProxyConfigModel.removeAdditionalSecret(index)
|
|
||||||
if (containerStatus === 1) {
|
|
||||||
root.mtProxyScheduleUpdate(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1404,13 +1468,7 @@ PageType {
|
|||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
image: "qrc:/images/controls/qr-code.svg"
|
image: "qrc:/images/controls/qr-code.svg"
|
||||||
imageColor: AmneziaStyle.color.paleGray
|
imageColor: AmneziaStyle.color.paleGray
|
||||||
onClicked: {
|
onClicked: settingsRoot.mtProxyShareQr(settingsRoot.mtProxyTmeLinkForAdditional(modelData))
|
||||||
ExportController.generateQrFromString(settingsRoot.mtProxyTmeLinkForAdditional(modelData))
|
|
||||||
PageController.goToShareConnectionPage(
|
|
||||||
qsTr("Telegram connection link"),
|
|
||||||
qsTr("MTProxy connection link"),
|
|
||||||
"", "", "")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageButtonType {
|
ImageButtonType {
|
||||||
@@ -1419,10 +1477,7 @@ PageType {
|
|||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
image: "qrc:/images/controls/copy.svg"
|
image: "qrc:/images/controls/copy.svg"
|
||||||
imageColor: AmneziaStyle.color.paleGray
|
imageColor: AmneziaStyle.color.paleGray
|
||||||
onClicked: {
|
onClicked: settingsRoot.mtProxyCopyText(settingsRoot.mtProxyTmeLinkForAdditional(modelData))
|
||||||
GC.copyToClipBoard(settingsRoot.mtProxyTmeLinkForAdditional(modelData))
|
|
||||||
PageController.showNotificationMessage(qsTr("Copied"))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1459,13 +1514,7 @@ PageType {
|
|||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
image: "qrc:/images/controls/qr-code.svg"
|
image: "qrc:/images/controls/qr-code.svg"
|
||||||
imageColor: AmneziaStyle.color.paleGray
|
imageColor: AmneziaStyle.color.paleGray
|
||||||
onClicked: {
|
onClicked: settingsRoot.mtProxyShareQr(settingsRoot.mtProxyTgLinkForAdditional(modelData))
|
||||||
ExportController.generateQrFromString(settingsRoot.mtProxyTgLinkForAdditional(modelData))
|
|
||||||
PageController.goToShareConnectionPage(
|
|
||||||
qsTr("Telegram connection link"),
|
|
||||||
qsTr("MTProxy connection link"),
|
|
||||||
"", "", "")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageButtonType {
|
ImageButtonType {
|
||||||
@@ -1474,10 +1523,7 @@ PageType {
|
|||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
image: "qrc:/images/controls/copy.svg"
|
image: "qrc:/images/controls/copy.svg"
|
||||||
imageColor: AmneziaStyle.color.paleGray
|
imageColor: AmneziaStyle.color.paleGray
|
||||||
onClicked: {
|
onClicked: settingsRoot.mtProxyCopyText(settingsRoot.mtProxyTgLinkForAdditional(modelData))
|
||||||
GC.copyToClipBoard(settingsRoot.mtProxyTgLinkForAdditional(modelData))
|
|
||||||
PageController.showNotificationMessage(qsTr("Copied"))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1560,15 +1606,47 @@ PageType {
|
|||||||
headerText: qsTr("Workers count")
|
headerText: qsTr("Workers count")
|
||||||
textField.placeholderText: "2"
|
textField.placeholderText: "2"
|
||||||
textField.text: workers
|
textField.text: workers
|
||||||
textField.maximumLength: 3
|
textField.maximumLength: 2
|
||||||
|
textField.inputMethodHints: Qt.ImhDigitsOnly
|
||||||
textField.validator: IntValidator {
|
textField.validator: IntValidator {
|
||||||
bottom: 1
|
bottom: 0
|
||||||
top: MtProxyConfigModel.maxWorkers()
|
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: {
|
textField.onEditingFinished: {
|
||||||
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
|
var v = workersTextField.textField.text
|
||||||
if (textField.text !== workers) {
|
if (v !== "") {
|
||||||
workers = textField.text
|
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)
|
MtProxyConfigModel.setWorkers(workers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1824,7 +1902,7 @@ PageType {
|
|||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
visible: ServersUiController.isProcessedServerHasWriteAccess()
|
visible: ServersUiController.isProcessedServerHasWriteAccess()
|
||||||
enabled: !root.mtProxyNetworkBlocked
|
enabled: fieldsEditable && !root.mtProxyNetworkBlocked
|
||||||
text: qsTr("Save")
|
text: qsTr("Save")
|
||||||
clickedFunc: function () {
|
clickedFunc: function () {
|
||||||
if (root.mtProxyNetworkBlocked) {
|
if (root.mtProxyNetworkBlocked) {
|
||||||
@@ -1920,6 +1998,5 @@ PageType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import PageEnum 1.0
|
|||||||
import ContainerProps 1.0
|
import ContainerProps 1.0
|
||||||
import ProtocolEnum 1.0
|
import ProtocolEnum 1.0
|
||||||
import Style 1.0
|
import Style 1.0
|
||||||
|
import TelemtConfig 1.0
|
||||||
|
|
||||||
import "./"
|
import "./"
|
||||||
import "../Controls2"
|
import "../Controls2"
|
||||||
@@ -19,6 +20,7 @@ PageType {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
property int containerStatus: 1
|
property int containerStatus: 1
|
||||||
|
property int statusErrorCode: 0
|
||||||
property bool isUpdating: false
|
property bool isUpdating: false
|
||||||
property bool isCheckingStatus: false
|
property bool isCheckingStatus: false
|
||||||
property bool isFetchingSecret: false
|
property bool isFetchingSecret: false
|
||||||
@@ -151,6 +153,29 @@ PageType {
|
|||||||
root.telemtScheduleContainerStatusRefresh()
|
root.telemtScheduleContainerStatusRefresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property var telemtPersistedAdditionalHex: []
|
||||||
|
|
||||||
|
function telemtRefreshPersistedAdditionalSecrets() {
|
||||||
|
var list = TelemtConfigModel.additionalSecretsList()
|
||||||
|
var a = []
|
||||||
|
for (var i = 0; i < list.length; ++i) {
|
||||||
|
a.push(String(list[i]))
|
||||||
|
}
|
||||||
|
root.telemtPersistedAdditionalHex = a
|
||||||
|
}
|
||||||
|
|
||||||
|
function telemtIsPersistedAdditionalHex(hex) {
|
||||||
|
var h = String(hex)
|
||||||
|
for (var j = 0; j < root.telemtPersistedAdditionalHex.length; ++j) {
|
||||||
|
if (String(root.telemtPersistedAdditionalHex[j]) === h) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property var natIpv4InputFormat: /^(\d{1,3}\.){0,3}\d{0,3}$/
|
||||||
|
|
||||||
function telemtScheduleUpdate(closePage) {
|
function telemtScheduleUpdate(closePage) {
|
||||||
var cp = closePage === undefined ? false : closePage
|
var cp = closePage === undefined ? false : closePage
|
||||||
Qt.callLater(function () {
|
Qt.callLater(function () {
|
||||||
@@ -158,6 +183,33 @@ PageType {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function natIpv4FieldShowInvalidError(text) {
|
||||||
|
var t = text ? String(text).replace(/^\s+|\s+$/g, '') : ""
|
||||||
|
if (t === "")
|
||||||
|
return false
|
||||||
|
if (TelemtConfigModel.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() {
|
function statusText() {
|
||||||
if (isCheckingStatus) {
|
if (isCheckingStatus) {
|
||||||
return qsTr("Checking...")
|
return qsTr("Checking...")
|
||||||
@@ -189,6 +241,10 @@ PageType {
|
|||||||
root.savedTlsDomain = TelemtConfigModel.getTlsDomain()
|
root.savedTlsDomain = TelemtConfigModel.getTlsDomain()
|
||||||
root.savedPublicHost = TelemtConfigModel.getPublicHost()
|
root.savedPublicHost = TelemtConfigModel.getPublicHost()
|
||||||
|
|
||||||
|
Qt.callLater(function () {
|
||||||
|
root.telemtRefreshPersistedAdditionalSecrets()
|
||||||
|
})
|
||||||
|
|
||||||
Qt.callLater(root.telemtOnPageShown)
|
Qt.callLater(root.telemtOnPageShown)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,6 +261,7 @@ PageType {
|
|||||||
isCheckingStatus = false
|
isCheckingStatus = false
|
||||||
isFetchingSecret = false
|
isFetchingSecret = false
|
||||||
busyIndicatorShown = false
|
busyIndicatorShown = false
|
||||||
|
statusErrorCode = 0
|
||||||
PageController.disableControls(false)
|
PageController.disableControls(false)
|
||||||
PageController.showBusyIndicator(false)
|
PageController.showBusyIndicator(false)
|
||||||
diagLoading = false
|
diagLoading = false
|
||||||
@@ -245,10 +302,8 @@ PageType {
|
|||||||
root.savedTransportMode = TelemtConfigModel.getTransportMode()
|
root.savedTransportMode = TelemtConfigModel.getTransportMode()
|
||||||
root.savedTlsDomain = TelemtConfigModel.getTlsDomain()
|
root.savedTlsDomain = TelemtConfigModel.getTlsDomain()
|
||||||
root.savedPublicHost = TelemtConfigModel.getPublicHost()
|
root.savedPublicHost = TelemtConfigModel.getPublicHost()
|
||||||
|
root.telemtRefreshPersistedAdditionalSecrets()
|
||||||
PageController.showNotificationMessage(message)
|
PageController.showNotificationMessage(message)
|
||||||
if (closePage) {
|
|
||||||
PageController.closePage()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onInstallationErrorOccurred() {
|
function onInstallationErrorOccurred() {
|
||||||
@@ -294,13 +349,18 @@ PageType {
|
|||||||
enabled ? qsTr("Telemt started") : qsTr("Telemt stopped"))
|
enabled ? qsTr("Telemt started") : qsTr("Telemt stopped"))
|
||||||
}
|
}
|
||||||
|
|
||||||
function onContainerStatusRefreshed(status) {
|
function onContainerStatusRefreshed(status, errorCode) {
|
||||||
if (!root.visible) {
|
if (!root.visible) {
|
||||||
isCheckingStatus = false
|
isCheckingStatus = false
|
||||||
isFetchingSecret = false
|
isFetchingSecret = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
containerStatus = status
|
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 = TelemtConfigModel.getTransportMode()
|
root.savedTransportMode = TelemtConfigModel.getTransportMode()
|
||||||
root.savedTlsDomain = TelemtConfigModel.getTlsDomain()
|
root.savedTlsDomain = TelemtConfigModel.getTlsDomain()
|
||||||
@@ -784,14 +844,48 @@ PageType {
|
|||||||
reuseItems: false
|
reuseItems: false
|
||||||
|
|
||||||
delegate: ColumnLayout {
|
delegate: ColumnLayout {
|
||||||
|
id: settingsRoot
|
||||||
width: settingsListView.width
|
width: settingsListView.width
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
|
readonly property bool fieldsEditable: isEnabled && containerStatus === 1 && !root.pageBusy
|
||||||
|
|
||||||
function telemtActiveSecretForBaseHex(baseHex) {
|
function telemtActiveSecretForBaseHex(baseHex) {
|
||||||
return root.telemtClientSecretForTabIndex(baseHex, root.syncedSecretTabIndex,
|
return root.telemtClientSecretForTabIndex(baseHex, root.syncedSecretTabIndex,
|
||||||
root.savedTlsDomain, TelemtConfigModel.defaultTlsDomain())
|
root.savedTlsDomain, TelemtConfigModel.defaultTlsDomain())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function telemtEffectiveHostForLinks() {
|
||||||
|
return root.savedPublicHost !== "" ? root.savedPublicHost : ServersUiController.serverHostName(ServersUiController.processedServerId)
|
||||||
|
}
|
||||||
|
|
||||||
|
function telemtTmeLinkForAdditional(baseHex) {
|
||||||
|
return "https://t.me/proxy?server=" + telemtEffectiveHostForLinks() + "&port=" + port + "&secret=" + telemtActiveSecretForBaseHex(baseHex)
|
||||||
|
}
|
||||||
|
|
||||||
|
function telemtTgLinkForAdditional(baseHex) {
|
||||||
|
return "tg://proxy?server=" + telemtEffectiveHostForLinks() + "&port=" + port + "&secret=" + telemtActiveSecretForBaseHex(baseHex)
|
||||||
|
}
|
||||||
|
|
||||||
|
function telemtIsAdditionalPersisted(hex) {
|
||||||
|
return root.telemtIsPersistedAdditionalHex(hex)
|
||||||
|
}
|
||||||
|
|
||||||
|
function telemtCopyText(text) {
|
||||||
|
GC.copyToClipBoard(text)
|
||||||
|
PageController.showNotificationMessage(qsTr("Copied"))
|
||||||
|
}
|
||||||
|
|
||||||
|
function telemtShareQr(link) {
|
||||||
|
ExportController.generateQrFromString(link)
|
||||||
|
PageController.goToShareConnectionPage(qsTr("Telegram connection link"),
|
||||||
|
qsTr("Telemt connection link"), "", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
function telemtRemoveAdditionalSecret(idx) {
|
||||||
|
TelemtConfigModel.removeAdditionalSecret(idx)
|
||||||
|
}
|
||||||
|
|
||||||
SwitcherType {
|
SwitcherType {
|
||||||
id: enableTelemtSwitch
|
id: enableTelemtSwitch
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@@ -820,6 +914,21 @@ PageType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CaptionTextType {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
Layout.bottomMargin: 8
|
||||||
|
visible: !fieldsEditable && !root.pageBusy
|
||||||
|
text: (containerStatus === 1 || containerStatus === 2)
|
||||||
|
? qsTr("Enable Telemt 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 {
|
ColumnLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 16
|
Layout.topMargin: 16
|
||||||
@@ -854,6 +963,7 @@ PageType {
|
|||||||
image: "qrc:/images/controls/refresh-cw.svg"
|
image: "qrc:/images/controls/refresh-cw.svg"
|
||||||
imageColor: AmneziaStyle.color.paleGray
|
imageColor: AmneziaStyle.color.paleGray
|
||||||
visible: ServersUiController.isProcessedServerHasWriteAccess()
|
visible: ServersUiController.isProcessedServerHasWriteAccess()
|
||||||
|
enabled: fieldsEditable
|
||||||
onClicked: {
|
onClicked: {
|
||||||
var secretSnapshot = secret
|
var secretSnapshot = secret
|
||||||
showQuestionDrawer(
|
showQuestionDrawer(
|
||||||
@@ -882,6 +992,7 @@ PageType {
|
|||||||
|
|
||||||
TextFieldWithHeaderType {
|
TextFieldWithHeaderType {
|
||||||
id: publicHostTextField
|
id: publicHostTextField
|
||||||
|
enabled: fieldsEditable
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
@@ -889,8 +1000,26 @@ PageType {
|
|||||||
headerText: qsTr("Public host / IP")
|
headerText: qsTr("Public host / IP")
|
||||||
textField.placeholderText: ServersUiController.serverHostName(ServersUiController.processedServerId)
|
textField.placeholderText: ServersUiController.serverHostName(ServersUiController.processedServerId)
|
||||||
textField.text: publicHost
|
textField.text: publicHost
|
||||||
|
textField.maximumLength: 253
|
||||||
|
textField.validator: PublicHostInputValidator {
|
||||||
|
}
|
||||||
|
textField.onTextChanged: {
|
||||||
|
var t = publicHostTextField.textField.text
|
||||||
|
if (TelemtConfigModel.isPublicHostTypingIncomplete(t)) {
|
||||||
|
publicHostTextField.errorText = ""
|
||||||
|
} else if (!TelemtConfigModel.isValidPublicHost(t)) {
|
||||||
|
publicHostTextField.errorText = qsTr("Enter a valid IP address or domain name")
|
||||||
|
} else {
|
||||||
|
publicHostTextField.errorText = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
|
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
|
||||||
|
if (!TelemtConfigModel.isValidPublicHost(textField.text)) {
|
||||||
|
publicHostTextField.errorText = qsTr("Enter a valid IP address or domain name")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
publicHostTextField.errorText = ""
|
||||||
if (textField.text !== publicHost) {
|
if (textField.text !== publicHost) {
|
||||||
publicHost = textField.text
|
publicHost = textField.text
|
||||||
TelemtConfigModel.setPublicHost(publicHost)
|
TelemtConfigModel.setPublicHost(publicHost)
|
||||||
@@ -925,6 +1054,7 @@ PageType {
|
|||||||
|
|
||||||
TextFieldWithHeaderType {
|
TextFieldWithHeaderType {
|
||||||
id: portTextField
|
id: portTextField
|
||||||
|
enabled: fieldsEditable
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
@@ -932,6 +1062,7 @@ PageType {
|
|||||||
headerText: qsTr("Server port")
|
headerText: qsTr("Server port")
|
||||||
textField.placeholderText: TelemtConfigModel.defaultPort()
|
textField.placeholderText: TelemtConfigModel.defaultPort()
|
||||||
textField.maximumLength: 5
|
textField.maximumLength: 5
|
||||||
|
textField.inputMethodHints: Qt.ImhDigitsOnly
|
||||||
textField.validator: IntValidator {
|
textField.validator: IntValidator {
|
||||||
bottom: 1
|
bottom: 1
|
||||||
top: 65535
|
top: 65535
|
||||||
@@ -940,14 +1071,17 @@ PageType {
|
|||||||
var savedPort = port
|
var savedPort = port
|
||||||
textField.text = (savedPort === TelemtConfigModel.defaultPort()) ? "" : savedPort
|
textField.text = (savedPort === TelemtConfigModel.defaultPort()) ? "" : savedPort
|
||||||
}
|
}
|
||||||
textField.onEditingFinished: {
|
textField.onTextChanged: {
|
||||||
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
|
var cur = portTextField.textField.text
|
||||||
var portValue = textField.text === "" ? TelemtConfigModel.defaultPort() : textField.text
|
var clean = TelemtConfigModel.sanitizePortFieldText(cur)
|
||||||
if (portValue !== port) {
|
if (clean !== cur) {
|
||||||
port = portValue
|
textField.text = clean
|
||||||
TelemtConfigModel.setPort(port)
|
textField.cursorPosition = clean.length
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
textField.onEditingFinished: {
|
||||||
|
textField.text = TelemtConfigModel.sanitizePortFieldText(textField.text)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CaptionTextType {
|
CaptionTextType {
|
||||||
@@ -962,20 +1096,62 @@ PageType {
|
|||||||
wrapMode: Text.WordWrap
|
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 {
|
TextFieldWithHeaderType {
|
||||||
id: tagTextField
|
id: tagTextField
|
||||||
|
enabled: fieldsEditable
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.bottomMargin: 16
|
Layout.bottomMargin: 16
|
||||||
headerText: qsTr("Promoted channel tag (optional)")
|
headerText: qsTr("Promoted channel tag (optional)")
|
||||||
textField.placeholderText: qsTr("leave empty if not needed")
|
textField.placeholderText: qsTr("32 hex chars from @MTProxyBot (e.g. 3b7b2fa9…)")
|
||||||
textField.text: tag
|
textField.text: tag
|
||||||
textField.maximumLength: 64
|
textField.maximumLength: TelemtConfigModel.mtProxyBotTagHexLength()
|
||||||
|
textField.onTextChanged: {
|
||||||
|
var cur = tagTextField.textField.text
|
||||||
|
var clean = TelemtConfigModel.sanitizeMtProxyTagFieldText(cur)
|
||||||
|
if (clean !== cur) {
|
||||||
|
textField.text = clean
|
||||||
|
textField.cursorPosition = clean.length
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var tt = tagTextField.textField.text
|
||||||
|
if (tt === "") {
|
||||||
|
tagTextField.errorText = ""
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (TelemtConfigModel.isMtProxyTagTypingIncomplete(tt)) {
|
||||||
|
tagTextField.errorText = ""
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!TelemtConfigModel.isValidMtProxyTag(tt)) {
|
||||||
|
tagTextField.errorText = qsTr("Proxy tag must be exactly 32 hexadecimal characters (0-9, A-F).")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tagTextField.errorText = ""
|
||||||
|
}
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
|
var raw = textField.text.replace(/^\s+|\s+$/g, '')
|
||||||
if (textField.text !== tag) {
|
var normalized = TelemtConfigModel.sanitizeMtProxyTagFieldText(raw)
|
||||||
tag = textField.text
|
textField.text = normalized
|
||||||
|
if (!TelemtConfigModel.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
|
||||||
TelemtConfigModel.setTag(tag)
|
TelemtConfigModel.setTag(tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1005,17 +1181,27 @@ PageType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
DropDownType {
|
||||||
id: transportModeDropDown
|
id: transportModeDropDown
|
||||||
|
enabled: fieldsEditable
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: 16 * 2
|
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.bottomMargin: 16
|
Layout.bottomMargin: 16
|
||||||
|
|
||||||
drawerParent: root
|
drawerParent: root
|
||||||
drawerHeight: 0.35
|
drawerHeight: 0.4
|
||||||
descriptionText: qsTr("Transport mode")
|
headerText: qsTr("Transport mode")
|
||||||
text: transportMode === "faketls" ? qsTr("FakeTLS") : qsTr("Standard MTProto")
|
text: transportMode === "faketls" ? qsTr("FakeTLS") : qsTr("Standard MTProto")
|
||||||
|
|
||||||
listView: Component {
|
listView: Component {
|
||||||
@@ -1043,6 +1229,7 @@ PageType {
|
|||||||
|
|
||||||
TextFieldWithHeaderType {
|
TextFieldWithHeaderType {
|
||||||
id: tlsDomainTextField
|
id: tlsDomainTextField
|
||||||
|
enabled: fieldsEditable
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
@@ -1050,13 +1237,30 @@ PageType {
|
|||||||
visible: transportMode === "faketls"
|
visible: transportMode === "faketls"
|
||||||
headerText: qsTr("FakeTLS domain")
|
headerText: qsTr("FakeTLS domain")
|
||||||
textField.placeholderText: root.previousTlsDomain
|
textField.placeholderText: root.previousTlsDomain
|
||||||
|
textField.validator: RegularExpressionValidator {
|
||||||
|
regularExpression: /^[A-Za-z0-9.-]*$/
|
||||||
|
}
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
var savedDomain = tlsDomain
|
var savedDomain = tlsDomain
|
||||||
textField.text = (savedDomain === TelemtConfigModel.defaultTlsDomain() || savedDomain === "") ? "" : savedDomain
|
textField.text = (savedDomain === TelemtConfigModel.defaultTlsDomain() || savedDomain === "") ? "" : savedDomain
|
||||||
}
|
}
|
||||||
|
textField.onTextChanged: {
|
||||||
|
var t = tlsDomainTextField.textField.text
|
||||||
|
if (t === "" || TelemtConfigModel.isFakeTlsDomainTypingIncomplete(t)
|
||||||
|
|| TelemtConfigModel.isValidFakeTlsDomain(t)) {
|
||||||
|
tlsDomainTextField.errorText = ""
|
||||||
|
} else {
|
||||||
|
tlsDomainTextField.errorText = qsTr("Enter a valid domain name")
|
||||||
|
}
|
||||||
|
}
|
||||||
textField.onEditingFinished: {
|
textField.onEditingFinished: {
|
||||||
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
|
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
|
||||||
var domainValue = textField.text === "" ? TelemtConfigModel.defaultTlsDomain() : textField.text
|
var domainValue = textField.text === "" ? TelemtConfigModel.defaultTlsDomain() : textField.text
|
||||||
|
if (!TelemtConfigModel.isValidFakeTlsDomain(domainValue)) {
|
||||||
|
tlsDomainTextField.errorText = qsTr("Enter a valid domain name")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tlsDomainTextField.errorText = ""
|
||||||
if (domainValue !== tlsDomain) {
|
if (domainValue !== tlsDomain) {
|
||||||
tlsDomain = domainValue
|
tlsDomain = domainValue
|
||||||
TelemtConfigModel.setTlsDomain(tlsDomain)
|
TelemtConfigModel.setTlsDomain(tlsDomain)
|
||||||
@@ -1108,6 +1312,7 @@ PageType {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: 0
|
spacing: 0
|
||||||
visible: advancedHeader.expanded
|
visible: advancedHeader.expanded
|
||||||
|
enabled: fieldsEditable
|
||||||
|
|
||||||
CaptionTextType {
|
CaptionTextType {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@@ -1131,37 +1336,196 @@ PageType {
|
|||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: additionalSecrets
|
model: additionalSecrets
|
||||||
delegate: RowLayout {
|
delegate: ColumnLayout {
|
||||||
|
id: addSecretDelegate
|
||||||
|
property bool linksExpanded: false
|
||||||
|
readonly property bool linksPanelAllowed: settingsRoot.telemtIsAdditionalPersisted(modelData)
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.bottomMargin: 4
|
Layout.bottomMargin: 8
|
||||||
spacing: 8
|
spacing: 0
|
||||||
CaptionTextType {
|
|
||||||
|
onLinksPanelAllowedChanged: {
|
||||||
|
if (!linksPanelAllowed) {
|
||||||
|
linksExpanded = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: modelData
|
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: settingsRoot.telemtActiveSecretForBaseHex(modelData)
|
||||||
color: AmneziaStyle.color.paleGray
|
color: AmneziaStyle.color.paleGray
|
||||||
elide: Text.ElideMiddle
|
elide: Text.ElideMiddle
|
||||||
font.pixelSize: 13
|
font.pixelSize: 13
|
||||||
}
|
}
|
||||||
ImageButtonType {
|
|
||||||
implicitWidth: 32
|
Image {
|
||||||
implicitHeight: 32
|
width: 24
|
||||||
hoverEnabled: true
|
height: 24
|
||||||
image: "qrc:/images/controls/copy.svg"
|
visible: addSecretDelegate.linksPanelAllowed
|
||||||
imageColor: AmneziaStyle.color.mutedGray
|
source: "qrc:/images/controls/chevron-down.svg"
|
||||||
onClicked: { GC.copyToClipBoard(modelData)
|
sourceSize.width: 24
|
||||||
PageController.showNotificationMessage(qsTr("Copied")) }
|
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 {
|
ImageButtonType {
|
||||||
implicitWidth: 32
|
implicitWidth: 32
|
||||||
implicitHeight: 32
|
implicitHeight: 32
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
image: "qrc:/images/controls/trash.svg"
|
image: "qrc:/images/controls/trash.svg"
|
||||||
imageColor: AmneziaStyle.color.vibrantRed
|
imageColor: AmneziaStyle.color.vibrantRed
|
||||||
onClicked: {
|
onClicked: settingsRoot.telemtRemoveAdditionalSecret(index)
|
||||||
TelemtConfigModel.removeAdditionalSecret(index)
|
}
|
||||||
root.telemtScheduleUpdate(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.telemtTmeLinkForAdditional(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: settingsRoot.telemtShareQr(settingsRoot.telemtTmeLinkForAdditional(modelData))
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageButtonType {
|
||||||
|
implicitWidth: 36
|
||||||
|
implicitHeight: 36
|
||||||
|
hoverEnabled: true
|
||||||
|
image: "qrc:/images/controls/copy.svg"
|
||||||
|
imageColor: AmneziaStyle.color.paleGray
|
||||||
|
onClicked: settingsRoot.telemtCopyText(settingsRoot.telemtTmeLinkForAdditional(modelData))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.telemtTgLinkForAdditional(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: settingsRoot.telemtShareQr(settingsRoot.telemtTgLinkForAdditional(modelData))
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageButtonType {
|
||||||
|
implicitWidth: 36
|
||||||
|
implicitHeight: 36
|
||||||
|
hoverEnabled: true
|
||||||
|
image: "qrc:/images/controls/copy.svg"
|
||||||
|
imageColor: AmneziaStyle.color.paleGray
|
||||||
|
onClicked: settingsRoot.telemtCopyText(settingsRoot.telemtTgLinkForAdditional(modelData))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1177,7 +1541,6 @@ PageType {
|
|||||||
text: qsTr("Add additional secret")
|
text: qsTr("Add additional secret")
|
||||||
clickedFunc: function () {
|
clickedFunc: function () {
|
||||||
TelemtConfigModel.addAdditionalSecret()
|
TelemtConfigModel.addAdditionalSecret()
|
||||||
root.telemtScheduleUpdate(false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1243,15 +1606,47 @@ PageType {
|
|||||||
headerText: qsTr("Workers count")
|
headerText: qsTr("Workers count")
|
||||||
textField.placeholderText: "2"
|
textField.placeholderText: "2"
|
||||||
textField.text: workers
|
textField.text: workers
|
||||||
textField.maximumLength: 3
|
textField.maximumLength: 2
|
||||||
|
textField.inputMethodHints: Qt.ImhDigitsOnly
|
||||||
textField.validator: IntValidator {
|
textField.validator: IntValidator {
|
||||||
bottom: 1
|
bottom: 0
|
||||||
top: TelemtConfigModel.maxWorkers()
|
top: TelemtConfigModel.maxWorkers()
|
||||||
}
|
}
|
||||||
|
textField.onTextChanged: {
|
||||||
|
var cur = workersTextField.textField.text
|
||||||
|
if (cur === "") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var n = parseInt(cur, 10)
|
||||||
|
var maxW = TelemtConfigModel.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: {
|
textField.onEditingFinished: {
|
||||||
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
|
var v = workersTextField.textField.text
|
||||||
if (textField.text !== workers) {
|
if (v !== "") {
|
||||||
workers = textField.text
|
var m = parseInt(v, 10)
|
||||||
|
var maxW2 = TelemtConfigModel.maxWorkers()
|
||||||
|
if (isNaN(m) || m < 0) {
|
||||||
|
m = 0
|
||||||
|
}
|
||||||
|
if (m > maxW2) {
|
||||||
|
m = maxW2
|
||||||
|
}
|
||||||
|
v = String(m)
|
||||||
|
textField.text = v
|
||||||
|
}
|
||||||
|
if (v !== workers) {
|
||||||
|
workers = v
|
||||||
TelemtConfigModel.setWorkers(workers)
|
TelemtConfigModel.setWorkers(workers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1288,8 +1683,24 @@ PageType {
|
|||||||
headerText: qsTr("Internal IP")
|
headerText: qsTr("Internal IP")
|
||||||
textField.placeholderText: "172.17.0.2"
|
textField.placeholderText: "172.17.0.2"
|
||||||
textField.text: natInternalIp
|
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.onEditingFinished: {
|
||||||
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
|
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
|
||||||
|
if (!TelemtConfigModel.isValidOptionalIpv4(textField.text)) {
|
||||||
|
natInternalIpTextField.errorText = qsTr("Enter a valid IPv4 address")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
natInternalIpTextField.errorText = ""
|
||||||
if (textField.text !== natInternalIp) {
|
if (textField.text !== natInternalIp) {
|
||||||
natInternalIp = textField.text
|
natInternalIp = textField.text
|
||||||
TelemtConfigModel.setNatInternalIp(natInternalIp)
|
TelemtConfigModel.setNatInternalIp(natInternalIp)
|
||||||
@@ -1307,8 +1718,24 @@ PageType {
|
|||||||
headerText: qsTr("External IP")
|
headerText: qsTr("External IP")
|
||||||
textField.placeholderText: "1.2.3.4"
|
textField.placeholderText: "1.2.3.4"
|
||||||
textField.text: natExternalIp
|
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.onEditingFinished: {
|
||||||
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
|
textField.text = textField.text.replace(/^\s+|\s+$/g, '')
|
||||||
|
if (!TelemtConfigModel.isValidOptionalIpv4(textField.text)) {
|
||||||
|
natExternalIpTextField.errorText = qsTr("Enter a valid IPv4 address")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
natExternalIpTextField.errorText = ""
|
||||||
if (textField.text !== natExternalIp) {
|
if (textField.text !== natExternalIp) {
|
||||||
natExternalIp = textField.text
|
natExternalIp = textField.text
|
||||||
TelemtConfigModel.setNatExternalIp(natExternalIp)
|
TelemtConfigModel.setNatExternalIp(natExternalIp)
|
||||||
@@ -1475,17 +1902,66 @@ PageType {
|
|||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
Layout.leftMargin: 16
|
Layout.leftMargin: 16
|
||||||
visible: ServersUiController.isProcessedServerHasWriteAccess()
|
visible: ServersUiController.isProcessedServerHasWriteAccess()
|
||||||
|
enabled: fieldsEditable && !root.telemtNetworkBlocked
|
||||||
text: qsTr("Save")
|
text: qsTr("Save")
|
||||||
clickedFunc: function () {
|
clickedFunc: function () {
|
||||||
|
if (root.telemtNetworkBlocked) {
|
||||||
|
PageController.showErrorMessage(qsTr("No internet connection. Connect to the internet to change Telemt settings."))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
publicHostTextField.errorText = ""
|
||||||
|
tagTextField.errorText = ""
|
||||||
|
tlsDomainTextField.errorText = ""
|
||||||
|
natInternalIpTextField.errorText = ""
|
||||||
|
natExternalIpTextField.errorText = ""
|
||||||
|
portTextField.errorText = ""
|
||||||
|
|
||||||
var portValue = portTextField.textField.text === ""
|
var portValue = portTextField.textField.text === ""
|
||||||
? TelemtConfigModel.defaultPort()
|
? TelemtConfigModel.defaultPort()
|
||||||
: portTextField.textField.text
|
: portTextField.textField.text
|
||||||
|
|
||||||
|
var errorLines = []
|
||||||
|
var bullet = "- "
|
||||||
if (!portTextField.textField.acceptableInput && portTextField.textField.text !== "") {
|
if (!portTextField.textField.acceptableInput && portTextField.textField.text !== "") {
|
||||||
portTextField.errorText = qsTr("The port must be in the range of 1 to 65535")
|
var portErr = qsTr("The port must be in the range of 1 to 65535")
|
||||||
|
portTextField.errorText = portErr
|
||||||
|
errorLines.push(bullet + portErr)
|
||||||
|
}
|
||||||
|
if (!TelemtConfigModel.isValidPublicHost(publicHostTextField.textField.text)) {
|
||||||
|
var hostErr = qsTr("Enter a valid IP address or domain name")
|
||||||
|
publicHostTextField.errorText = hostErr
|
||||||
|
errorLines.push(bullet + hostErr)
|
||||||
|
}
|
||||||
|
var tagNormalized = TelemtConfigModel.sanitizeMtProxyTagFieldText(tagTextField.textField.text)
|
||||||
|
tagTextField.textField.text = tagNormalized
|
||||||
|
if (!TelemtConfigModel.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 === ""
|
||||||
|
? TelemtConfigModel.defaultTlsDomain()
|
||||||
|
: tlsDomainTextField.textField.text
|
||||||
|
if (!TelemtConfigModel.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 (!TelemtConfigModel.isValidOptionalIpv4(natInternalIpTextField.textField.text)) {
|
||||||
|
natInternalIpTextField.errorText = natIpErr
|
||||||
|
errorLines.push(bullet + qsTr("NAT internal IP: enter a valid IPv4 address"))
|
||||||
|
}
|
||||||
|
if (!TelemtConfigModel.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
|
return
|
||||||
}
|
}
|
||||||
TelemtConfigModel.setPort(portValue)
|
TelemtConfigModel.setPort(portValue)
|
||||||
TelemtConfigModel.setTag(tagTextField.textField.text)
|
TelemtConfigModel.setTag(tagNormalized)
|
||||||
TelemtConfigModel.setPublicHost(publicHostTextField.textField.text)
|
TelemtConfigModel.setPublicHost(publicHostTextField.textField.text)
|
||||||
TelemtConfigModel.setTransportMode(transportMode)
|
TelemtConfigModel.setTransportMode(transportMode)
|
||||||
var domainValue = tlsDomainTextField.textField.text === ""
|
var domainValue = tlsDomainTextField.textField.text === ""
|
||||||
@@ -1522,6 +1998,5 @@ PageType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -320,7 +320,7 @@ PageType {
|
|||||||
Layout.rightMargin: 16
|
Layout.rightMargin: 16
|
||||||
visible: isQrCodeVisible
|
visible: isQrCodeVisible
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
text: qsTr("To read the QR code in the Amnezia app, select \"Add server\" → \"I have data to connect\" → \"QR code, key or settings file\"")
|
text: qsTr("To read the QR code in the Amnezia app, tap + in the main menu → 'QR code'")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user