mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-05-08 14:33:23 +00:00
* refactor: move business logic from servers model * refactor: move containersModel initialization * refactor: added protocol ui controller and removed settings class from protocols model * refactor: moved cli management to separate controller * refactor: moved app split to separate controller * refactor: moved site split to separate controller * refactor: moved allowed dns to separate controller * refactor: moved language logic to separate ui controller * refactor: removed Settings from devices model * refactor: moved configs and services api logit to separate core controller * refactor: added a layer with a repository between the storage and controllers * refactor: use child parent system instead of smart pointers for controllers and models initialization * refactor: moved install functions from server controller to install controller * refactor: install controller refactoring * chore: renamed exportController to exportUiController * refactor: separate export controller * refactor: removed VpnConfigurationsController * chore: renamed ServerController to SshSession * refactor: replaced ServerController to SshSession * chore: moved qml controllers to separate folder * chore: include fixes * chore: moved utils from core root to core/utils * chore: include fixes * chore: rename core/utils files to camelCase foramt * chore: include fixes * chore: moved some utils to api and selfhosted folders * chore: include fixes * chore: remove unused file * chore: moved serialization folder to core/utils * chore: include fixes * chore: moved some files from client root to core/utils * chore: include fixes * chore: moved ui utils to ui/utils folder * chore: include fixes * chore: move utils from root to ui/utils * chore: include fixes * chore: moved configurators to core/configurators * chore: include fixes * refactor: moved iap logic from ui controller to core * refactor: moved remaining core logic from ApiConfigsController to SubscriptionController * chore: rename apiNewsController to apiNewsUiController * refactor: moved core logic from news ui controller to core * chore: renamed apiConfigsController to subscriptionUiController * chore: include fixes * refactor: merge ApiSettingsController with SubscriptionUiController * chore: moved ui selfhosted controllers to separate folder * chore: include fixes * chore: rename connectionController to connectiomUiController * refactor: moved core logic from connectionUiController * chore: rename settingsController to settingsUiController * refactor: move core logic from settingsUiController * refactor: moved core controller signal/slot connections to separate class * fix: newsController fixes after refactoring * chore: rename model to camelCase * chore: include fixes * chore: remove unused code * chore: move selfhosted core to separate folder * chore: include fixes * chore: rename importController to importUiController * refactor: move core logic from importUiController * chore: minor fixes * chore: remove prem v1 migration * refactor: remove openvpn over cloak and openvpn over shadowsocks * refactor: removed protocolsForContainer function * refactor: add core models * refactor: replace json with c++ structs for server config * refactor: move getDnsPair to ServerConfigUtils * feat: add admin selfhosted config export test * feat: add multi import test * refactor: use coreController for tests * feat: add few simple tests * chore: qrepos in all core controllers * feat: add test for settings * refactor: remove repo dependency from configurators * chore: moved protocols to core folder * chore: include fixes * refactor: moved containersDefs, defs, apiDefs, protocolsDefs to different places * chore: include fixes * chore: build fixes * chore: build fixes * refactor: remove q repo and interface repo * feat: add test for ui servers model and controller * chore: renamed to camelCase * chore: include fixes * refactor: moved core logic from sites ui controller * fix: fixed api config processing * fix: fixed processed server index processing * refactor: protocol models now use c++ structs instead of json configs * refactor: servers model now use c++ struct instead of json config * fix: fixed default server index processing * fix: fix logs init * fix: fix secure settings load keys * chore: build fixes * fix: fixed clear settings * fix: fixed restore backup * fix: sshSession usage * fix: fixed export functions signatures * fix: return missing part from buildContainerWorker * fix: fixed server description on page home * refactor: add container config helpers functions * refactor: c++ structs instead of json * chore: add dns protocol config struct * refactor: move config utils functions to config structs * feat: add test for selfhosted server setup * refactor: separate resources.qrc * fix: fixed server rename * chore: return nameOverriddenByUser * fix: build fixes * fix: fixed models init * refactor: cleanup models usage * fix: fixed models init * chore: cleanup connections and functions signatures * chore: cleanup updateModel calls * feat: added cache to servers repo * chore: cleanup unused functions * chore: ssxray processing * chore: remove transportProtoWithDefault and portWithDefault functions * chore: removed proto types any and l2tp * refactor: moved some constants * fix: fixed native configs export * refactor: remove json from processConfigWith functions * fix: fixed processed server index usage * fix: qml warning fixes * chore: merge fixes * chore: update tests * fix: fixed xray config processing * fix: fixed split tunneling processing * chore: rename sites controllers and model * chore: rename fixes * chore: minor fixes * chore: remove ability to load backup from "file with connection settings" button * fix: fixed api device revoke * fix: remove full model update when renaming a user * fix: fixed premium/free server rename * fix: fixed selfhosted new server install * fix: fixed updateContainer function * fix: fixed revoke for external premium configs * feat: add native configs qr processing * chore: codestyle fixes * fix: fixed admin config create * chore: again remove ability to load backup from "file with connection settings" button * chore: minor fixes * fix: fixed variables initialization * fix: fixed qml imports * fix: minor fixes * fix: fix vpnConnection function calls * feat: add buckup error handling * fix: fixed admin config revok * fix: fixed selfhosted awg installation * fix: ad visability * feat: add empty check for primary dns * chore: minor fixes
426 lines
14 KiB
C++
426 lines
14 KiB
C++
#include "serversModel.h"
|
|
|
|
#include <QHash>
|
|
#include <QSet>
|
|
#include <QJsonDocument>
|
|
|
|
#include "core/models/serverConfig.h"
|
|
#include "core/utils/api/apiEnums.h"
|
|
#include "core/utils/constants/apiKeys.h"
|
|
#include "core/utils/constants/apiConstants.h"
|
|
#include "core/utils/selfhosted/sshSession.h"
|
|
#include "core/utils/networkUtilities.h"
|
|
|
|
#if defined(Q_OS_IOS) || defined(MACOS_NE)
|
|
#include <AmneziaVPN-Swift.h>
|
|
#endif
|
|
|
|
#include "core/utils/api/apiUtils.h"
|
|
|
|
using namespace amnezia;
|
|
|
|
namespace
|
|
{
|
|
namespace configKey
|
|
{
|
|
constexpr char apiConfig[] = "api_config";
|
|
constexpr char serviceInfo[] = "service_info";
|
|
constexpr char availableCountries[] = "available_countries";
|
|
constexpr char serverCountryCode[] = "server_country_code";
|
|
constexpr char serverCountryName[] = "server_country_name";
|
|
constexpr char userCountryCode[] = "user_country_code";
|
|
constexpr char serviceType[] = "service_type";
|
|
constexpr char serviceProtocol[] = "service_protocol";
|
|
|
|
constexpr char publicKeyInfo[] = "public_key";
|
|
constexpr char expiresAt[] = "expires_at";
|
|
}
|
|
|
|
QString normalizeVpnKey(const QString &vpnKey)
|
|
{
|
|
QString normalized = vpnKey.trimmed();
|
|
if (normalized.startsWith(QStringLiteral("vpn://"), Qt::CaseInsensitive)) {
|
|
normalized = normalized.mid(QStringLiteral("vpn://").size());
|
|
}
|
|
return normalized;
|
|
}
|
|
}
|
|
|
|
ServersModel::ServersModel(QObject *parent) : QAbstractListModel(parent)
|
|
{
|
|
connect(this, &ServersModel::defaultServerIndexChanged, this, &ServersModel::defaultServerNameChanged);
|
|
|
|
connect(this, &ServersModel::defaultServerIndexChanged, this, [this](const int serverIndex) {
|
|
if (serverIndex < 0 || serverIndex >= m_servers.size()) {
|
|
return;
|
|
}
|
|
auto defaultContainer = m_servers.at(serverIndex).defaultContainer();
|
|
emit ServersModel::defaultServerDefaultContainerChanged(defaultContainer);
|
|
emit ServersModel::defaultServerNameChanged();
|
|
});
|
|
|
|
connect(this, &ServersModel::processedServerIndexChanged, this, &ServersModel::processedServerChanged);
|
|
}
|
|
|
|
int ServersModel::rowCount(const QModelIndex &parent) const
|
|
{
|
|
Q_UNUSED(parent);
|
|
return static_cast<int>(m_servers.size());
|
|
}
|
|
|
|
QVariant ServersModel::data(const QModelIndex &index, int role) const
|
|
{
|
|
if (!index.isValid() || index.row() < 0 || index.row() >= static_cast<int>(m_servers.size())) {
|
|
return QVariant();
|
|
}
|
|
|
|
const ServerConfig &server = m_servers.at(index.row());
|
|
const int configVersion = server.configVersion();
|
|
|
|
switch (role) {
|
|
case NameRole: {
|
|
if (configVersion) {
|
|
if (server.isApiV1()) {
|
|
return server.as<ApiV1ServerConfig>()->name;
|
|
} else if (server.isApiV2()) {
|
|
return server.as<ApiV2ServerConfig>()->name;
|
|
}
|
|
}
|
|
QString name = server.description();
|
|
if (name.isEmpty()) {
|
|
return server.hostName();
|
|
}
|
|
return name;
|
|
}
|
|
case ServerDescriptionRole: {
|
|
auto description = getServerDescription(server, index.row());
|
|
return configVersion ? description : description + server.hostName();
|
|
}
|
|
case HostNameRole: return server.hostName();
|
|
case CredentialsRole: return QVariant::fromValue(serverCredentials(index.row()));
|
|
case CredentialsLoginRole: return serverCredentials(index.row()).userName;
|
|
case IsDefaultRole: return index.row() == m_defaultServerIndex;
|
|
case IsCurrentlyProcessedRole: return index.row() == m_processedServerIndex;
|
|
case HasWriteAccessRole: {
|
|
auto credentials = serverCredentials(index.row());
|
|
return (!credentials.userName.isEmpty() && !credentials.secretData.isEmpty());
|
|
}
|
|
case ContainsAmneziaDnsRole: {
|
|
QString primaryDns = server.dns1();
|
|
return primaryDns == protocols::dns::amneziaDnsIp;
|
|
}
|
|
case DefaultContainerRole: {
|
|
return server.defaultContainer();
|
|
}
|
|
case HasInstalledContainers: {
|
|
return serverHasInstalledContainers(index.row());
|
|
}
|
|
case IsServerFromTelegramApiRole: {
|
|
return configVersion == apiDefs::ConfigSource::Telegram;
|
|
}
|
|
case IsServerFromGatewayApiRole: {
|
|
return configVersion == apiDefs::ConfigSource::AmneziaGateway;
|
|
}
|
|
case ApiConfigRole: {
|
|
return QVariant();
|
|
}
|
|
case IsCountrySelectionAvailableRole: {
|
|
if (server.isApiV2()) {
|
|
return !server.as<ApiV2ServerConfig>()->apiConfig.availableCountries.isEmpty();
|
|
}
|
|
return false;
|
|
}
|
|
case ApiAvailableCountriesRole: {
|
|
if (server.isApiV2()) {
|
|
return server.as<ApiV2ServerConfig>()->apiConfig.availableCountries;
|
|
}
|
|
return QJsonArray();
|
|
}
|
|
case ApiServerCountryCodeRole: {
|
|
if (server.isApiV2()) {
|
|
return server.as<ApiV2ServerConfig>()->apiConfig.serverCountryCode;
|
|
}
|
|
return QString();
|
|
}
|
|
case HasAmneziaDns: {
|
|
QString primaryDns = server.dns1();
|
|
return primaryDns == protocols::dns::amneziaDnsIp;
|
|
}
|
|
case IsAdVisibleRole: {
|
|
if (server.isApiV2()) {
|
|
return server.as<ApiV2ServerConfig>()->apiConfig.serviceInfo.isAdVisible;
|
|
}
|
|
return false;
|
|
}
|
|
case AdHeaderRole: {
|
|
if (server.isApiV2()) {
|
|
return server.as<ApiV2ServerConfig>()->apiConfig.serviceInfo.adHeader;
|
|
}
|
|
return QString();
|
|
}
|
|
case AdDescriptionRole: {
|
|
if (server.isApiV2()) {
|
|
return server.as<ApiV2ServerConfig>()->apiConfig.serviceInfo.adDescription;
|
|
}
|
|
return QString();
|
|
}
|
|
case AdEndpointRole: {
|
|
if (server.isApiV2()) {
|
|
return server.as<ApiV2ServerConfig>()->apiConfig.serviceInfo.adEndpoint;
|
|
}
|
|
return QString();
|
|
}
|
|
case IsRenewalAvailableRole: {
|
|
if (server.isApiV2()) {
|
|
return server.as<ApiV2ServerConfig>()->apiConfig.serviceInfo.isRenewalAvailable;
|
|
}
|
|
return false;
|
|
}
|
|
case IsSubscriptionExpiredRole: {
|
|
if (!server.isApiV2()) {
|
|
return false;
|
|
}
|
|
|
|
const ApiConfig &apiConfig = server.as<ApiV2ServerConfig>()->apiConfig;
|
|
if (apiConfig.isInAppPurchase) {
|
|
return false;
|
|
}
|
|
if (apiConfig.subscriptionExpiredByServer) {
|
|
return true;
|
|
}
|
|
if (apiConfig.subscription.endDate.isEmpty()) {
|
|
return false;
|
|
}
|
|
return apiUtils::isSubscriptionExpired(apiConfig.subscription.endDate);
|
|
}
|
|
case IsSubscriptionExpiringSoonRole: {
|
|
if (!server.isApiV2()) {
|
|
return false;
|
|
}
|
|
|
|
const ApiConfig &apiConfig = server.as<ApiV2ServerConfig>()->apiConfig;
|
|
if (apiConfig.isInAppPurchase) {
|
|
return false;
|
|
}
|
|
if (apiConfig.subscription.endDate.isEmpty()) {
|
|
return false;
|
|
}
|
|
return apiUtils::isSubscriptionExpiringSoon(apiConfig.subscription.endDate);
|
|
}
|
|
}
|
|
|
|
return QVariant();
|
|
}
|
|
|
|
QVariant ServersModel::data(const int index, int role) const
|
|
{
|
|
QModelIndex modelIndex = this->index(index);
|
|
return data(modelIndex, role);
|
|
}
|
|
|
|
void ServersModel::updateModel(const QVector<ServerConfig> &servers, int defaultServerIndex, bool isAmneziaDnsEnabled)
|
|
{
|
|
beginResetModel();
|
|
m_servers = servers;
|
|
m_defaultServerIndex = defaultServerIndex;
|
|
m_isAmneziaDnsEnabled = isAmneziaDnsEnabled;
|
|
endResetModel();
|
|
emit defaultServerIndexChanged(m_defaultServerIndex);
|
|
emit processedServerChanged();
|
|
}
|
|
|
|
const int ServersModel::getDefaultServerIndex()
|
|
{
|
|
return m_defaultServerIndex;
|
|
}
|
|
|
|
QString ServersModel::getServerDescription(const ServerConfig &server, const int index) const
|
|
{
|
|
const int configVersion = server.configVersion();
|
|
QString description;
|
|
|
|
if (server.isApiV2()) {
|
|
const ApiV2ServerConfig *apiV2 = server.as<ApiV2ServerConfig>();
|
|
if (apiV2 && !apiV2->apiConfig.serverCountryCode.isEmpty()) {
|
|
return apiV2->apiConfig.serverCountryName;
|
|
}
|
|
return apiV2 ? apiV2->description : server.description();
|
|
} else if (server.isApiV1()) {
|
|
const ApiV1ServerConfig *apiV1 = server.as<ApiV1ServerConfig>();
|
|
return apiV1 ? apiV1->description : server.description();
|
|
} else if (data(index, HasWriteAccessRole).toBool()) {
|
|
QMap<DockerContainer, ContainerConfig> containers = server.containers();
|
|
bool isDnsInstalled = containers.contains(DockerContainer::Dns);
|
|
if (m_isAmneziaDnsEnabled && isDnsInstalled) {
|
|
description += "Amnezia DNS | ";
|
|
}
|
|
} else {
|
|
if (data(index, HasAmneziaDns).toBool()) {
|
|
description += "Amnezia DNS | ";
|
|
}
|
|
}
|
|
return description;
|
|
}
|
|
|
|
const int ServersModel::getServersCount()
|
|
{
|
|
return m_servers.size();
|
|
}
|
|
|
|
bool ServersModel::hasServerWithWriteAccess()
|
|
{
|
|
for (size_t i = 0; i < getServersCount(); i++) {
|
|
if (qvariant_cast<bool>(data(i, HasWriteAccessRole))) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ServersModel::setProcessedServerIndex(const int index)
|
|
{
|
|
if (m_processedServerIndex != index) {
|
|
m_processedServerIndex = index;
|
|
emit processedServerIndexChanged(m_processedServerIndex);
|
|
}
|
|
}
|
|
|
|
const ServerCredentials ServersModel::getProcessedServerCredentials()
|
|
{
|
|
return serverCredentials(m_processedServerIndex);
|
|
}
|
|
|
|
bool ServersModel::isDefaultServerCurrentlyProcessed()
|
|
{
|
|
return m_defaultServerIndex == m_processedServerIndex;
|
|
}
|
|
|
|
bool ServersModel::isDefaultServerFromApi()
|
|
{
|
|
return data(m_defaultServerIndex, IsServerFromTelegramApiRole).toBool()
|
|
|| data(m_defaultServerIndex, IsServerFromGatewayApiRole).toBool();
|
|
}
|
|
|
|
bool ServersModel::isProcessedServerHasWriteAccess()
|
|
{
|
|
return qvariant_cast<bool>(data(m_processedServerIndex, HasWriteAccessRole));
|
|
}
|
|
|
|
bool ServersModel::isDefaultServerHasWriteAccess()
|
|
{
|
|
return qvariant_cast<bool>(data(m_defaultServerIndex, HasWriteAccessRole));
|
|
}
|
|
|
|
QHash<int, QByteArray> ServersModel::roleNames() const
|
|
{
|
|
QHash<int, QByteArray> roles;
|
|
|
|
roles[NameRole] = "name";
|
|
roles[ServerDescriptionRole] = "serverDescription";
|
|
roles[CollapsedServerDescriptionRole] = "collapsedServerDescription";
|
|
roles[ExpandedServerDescriptionRole] = "expandedServerDescription";
|
|
|
|
roles[HostNameRole] = "hostName";
|
|
|
|
roles[CredentialsRole] = "credentials";
|
|
roles[CredentialsLoginRole] = "credentialsLogin";
|
|
|
|
roles[IsDefaultRole] = "isDefault";
|
|
roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed";
|
|
|
|
roles[HasWriteAccessRole] = "hasWriteAccess";
|
|
|
|
roles[ContainsAmneziaDnsRole] = "containsAmneziaDns";
|
|
|
|
roles[DefaultContainerRole] = "defaultContainer";
|
|
roles[HasInstalledContainers] = "hasInstalledContainers";
|
|
|
|
roles[IsServerFromTelegramApiRole] = "isServerFromTelegramApi";
|
|
roles[IsServerFromGatewayApiRole] = "isServerFromGatewayApi";
|
|
roles[ApiConfigRole] = "apiConfig";
|
|
roles[IsCountrySelectionAvailableRole] = "isCountrySelectionAvailable";
|
|
roles[ApiAvailableCountriesRole] = "apiAvailableCountries";
|
|
roles[ApiServerCountryCodeRole] = "apiServerCountryCode";
|
|
|
|
roles[IsAdVisibleRole] = "isAdVisible";
|
|
roles[AdHeaderRole] = "adHeader";
|
|
roles[AdDescriptionRole] = "adDescription";
|
|
roles[AdEndpointRole] = "adEndpoint";
|
|
roles[IsRenewalAvailableRole] = "isRenewalAvailable";
|
|
roles[IsSubscriptionExpiredRole] = "isSubscriptionExpired";
|
|
roles[IsSubscriptionExpiringSoonRole] = "isSubscriptionExpiringSoon";
|
|
|
|
return roles;
|
|
}
|
|
|
|
ServerCredentials ServersModel::serverCredentials(int index) const
|
|
{
|
|
if (index < 0 || index >= m_servers.size()) {
|
|
return ServerCredentials();
|
|
}
|
|
const ServerConfig &server = m_servers.at(index);
|
|
|
|
if (server.isSelfHosted()) {
|
|
const SelfHostedServerConfig *selfHosted = server.as<SelfHostedServerConfig>();
|
|
if (selfHosted) {
|
|
ServerCredentials credentials;
|
|
credentials.hostName = selfHosted->hostName;
|
|
credentials.userName = selfHosted->userName.value_or("");
|
|
credentials.secretData = selfHosted->password.value_or("");
|
|
credentials.port = selfHosted->port.value_or(22);
|
|
return credentials;
|
|
}
|
|
}
|
|
|
|
return ServerCredentials();
|
|
}
|
|
|
|
bool ServersModel::isServerFromApi(const int serverIndex)
|
|
{
|
|
return data(serverIndex, IsServerFromTelegramApiRole).toBool()
|
|
|| data(serverIndex, IsServerFromGatewayApiRole).toBool();
|
|
}
|
|
|
|
QVariant ServersModel::getDefaultServerData(const QString roleString)
|
|
{
|
|
auto roles = roleNames();
|
|
for (auto it = roles.begin(); it != roles.end(); it++) {
|
|
if (QString(it.value()) == roleString) {
|
|
return data(m_defaultServerIndex, it.key());
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
QVariant ServersModel::getProcessedServerData(const QString &roleString)
|
|
{
|
|
auto roles = roleNames();
|
|
for (auto it = roles.begin(); it != roles.end(); it++) {
|
|
if (QString(it.value()) == roleString) {
|
|
return data(m_processedServerIndex, it.key());
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
|
|
bool ServersModel::serverHasInstalledContainers(const int serverIndex) const
|
|
{
|
|
const ServerConfig &server = m_servers.at(serverIndex);
|
|
QMap<DockerContainer, ContainerConfig> containers = server.containers();
|
|
|
|
for (auto it = containers.begin(); it != containers.end(); ++it) {
|
|
DockerContainer container = it.key();
|
|
if (ContainerUtils::containerService(container) == ServiceType::Vpn) {
|
|
return true;
|
|
}
|
|
if (container == DockerContainer::SSXray) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|