add ui Configuration Files

This commit is contained in:
dranik
2026-05-13 13:17:37 +03:00
parent 8a29b49fd7
commit d0a9f6e4d5
5 changed files with 79 additions and 7 deletions

View File

@@ -1807,6 +1807,16 @@ Thank you for staying with us!</source>
<source>Cancel</source>
<translation>Отменить</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiDevices.qml" line="252"/>
<source>Configuration Files: %1</source>
<translation>Файлы конфигурации: %1</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsApiDevices.qml" line="253"/>
<source>Generated configuration files also count towards the device limit</source>
<translation>Сгенерированные файлы конфигурации тоже учитываются в лимите устройств</translation>
</message>
</context>
<context>
<name>PageSettingsApiInstructions</name>

View File

@@ -11,6 +11,8 @@
namespace
{
Logger logger("AccountInfoModel");
constexpr QLatin1String kCountryConfigSourceType("country_config");
}
ApiAccountInfoModel::ApiAccountInfoModel(QObject *parent) : QAbstractListModel(parent)
@@ -121,6 +123,9 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
const int spare = m_accountInfoData.maxDeviceCount - m_accountInfoData.activeDeviceCount;
return qMax(0, spare);
}
case ConfigurationFilesCountRole: {
return m_accountInfoData.configurationFilesCount;
}
}
return QVariant();
@@ -135,6 +140,15 @@ void ApiAccountInfoModel::updateModel(const QJsonObject &accountInfoObject, cons
m_availableCountries = accountInfoObject.value(apiDefs::key::availableCountries).toArray();
m_issuedConfigsInfo = accountInfoObject.value(apiDefs::key::issuedConfigs).toArray();
int configurationFilesCount = 0;
for (int i = 0; i < m_issuedConfigsInfo.size(); ++i) {
const QJsonObject issued = m_issuedConfigsInfo.at(i).toObject();
if (issued.value(apiDefs::key::sourceType).toString() == kCountryConfigSourceType) {
++configurationFilesCount;
}
}
accountInfoData.configurationFilesCount = configurationFilesCount;
accountInfoData.activeDeviceCount = accountInfoObject.value(apiDefs::key::activeDeviceCount).toInt();
accountInfoData.maxDeviceCount = accountInfoObject.value(apiDefs::key::maxDeviceCount).toInt();
accountInfoData.subscriptionEndDate = accountInfoObject.value(apiDefs::key::subscriptionEndDate).toString();
@@ -223,6 +237,7 @@ QHash<int, QByteArray> ApiAccountInfoModel::roleNames() const
roles[ActiveDeviceCountRole] = "activeDeviceCount";
roles[MaxDeviceCountRole] = "maxDeviceCount";
roles[AvailableDeviceSlotsRole] = "availableDeviceSlots";
roles[ConfigurationFilesCountRole] = "configurationFilesCount";
return roles;
}

View File

@@ -28,7 +28,8 @@ public:
IsInAppPurchaseRole,
ActiveDeviceCountRole,
MaxDeviceCountRole,
AvailableDeviceSlotsRole
AvailableDeviceSlotsRole,
ConfigurationFilesCountRole
};
explicit ApiAccountInfoModel(QObject *parent = nullptr);
@@ -67,6 +68,7 @@ private:
bool isInAppPurchase = false;
bool isRenewalAvailable = false;
int configurationFilesCount = 0;
};
AccountInfoData m_accountInfoData;

View File

@@ -241,6 +241,26 @@ PageType {
DividerType {}
}
footer: ColumnLayout {
width: listView.width
LabelWithButtonType {
Layout.fillWidth: true
Layout.topMargin: 8
text: qsTr("Configuration Files: %1").arg(ApiAccountInfoModel.data("configurationFilesCount"))
descriptionText: qsTr("Generated configuration files also count towards the device limit")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
SubscriptionUiController.updateApiCountryModel()
PageController.goToPage(PageEnum.PageSettingsApiNativeConfigs)
}
}
DividerType {}
}
}
function deactivateExternalDevice(serverIndex, supportTag, countryCode) {

View File

@@ -521,26 +521,51 @@ func handleAccountInfo(w http.ResponseWriter, r *http.Request) {
drainBody(r)
mu.Lock()
issuedConfigs := make([]issuedConfigInfo, 0, len(issued))
gatewayConfigs := make([]issuedConfigInfo, 0, len(issued))
for _, cfg := range issued {
issuedConfigs = append(issuedConfigs, cfg)
gatewayConfigs = append(gatewayConfigs, cfg)
}
mu.Unlock()
sort.Slice(issuedConfigs, func(i, j int) bool {
return issuedConfigs[i].InstallationUUID < issuedConfigs[j].InstallationUUID
sort.Slice(gatewayConfigs, func(i, j int) bool {
return gatewayConfigs[i].InstallationUUID < gatewayConfigs[j].InstallationUUID
})
// Seed country_config rows so the client can verify "Configuration Files: N" (ApiAccountInfoModel counts these).
// active_device_count must reflect gateway devices only, not these synthetic file rows.
nowISO := time.Now().UTC().Format(time.RFC3339)
mockCountryConfigs := []issuedConfigInfo{
{
InstallationUUID: "mock-country-config-de",
WorkerLastUpdated: nowISO,
LastDownloaded: nowISO,
SourceType: "country_config",
OSVersion: "",
ServerCountryCode: "de",
ServerCountryName: "Germany",
},
{
InstallationUUID: "mock-country-config-nl",
WorkerLastUpdated: nowISO,
LastDownloaded: nowISO,
SourceType: "country_config",
OSVersion: "",
ServerCountryCode: "nl",
ServerCountryName: "Netherlands",
},
}
allIssued := append(append([]issuedConfigInfo{}, gatewayConfigs...), mockCountryConfigs...)
// Keys match client/core/utils/constants/apiKeys.h (snake_case).
endDate := time.Now().UTC().AddDate(1, 0, 0).Format(time.RFC3339)
resp := map[string]any{
"active_device_count": len(issuedConfigs),
"active_device_count": len(gatewayConfigs),
"max_device_count": 5,
"subscription_end_date": endDate,
"subscription_description": "Local mock (tools/local_gateway)",
"is_renewal_available": false,
"supported_protocols": []string{"awg", "vless"},
"available_countries": []any{},
"issued_configs": issuedConfigs,
"issued_configs": allIssued,
"support_info": map[string]any{
"telegram": "amnezia_support",
"email": "support@example.com",