Merge pull request #12159 from AndriiLandiak/feature/edge-limit

Edges maximum number configuration
This commit is contained in:
Andrew Shvayka 2024-12-02 14:55:50 +01:00 committed by GitHub
commit 03ac89bdde
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 186 additions and 41 deletions

View File

@ -416,6 +416,9 @@ public class HomePageApiTest extends AbstractControllerTest {
Assert.assertEquals(DEFAULT_DASHBOARDS_COUNT, usageInfo.getDashboards());
Assert.assertEquals(configuration.getMaxDashboards(), usageInfo.getMaxDashboards());
Assert.assertEquals(0, usageInfo.getEdges());
Assert.assertEquals(configuration.getMaxEdges(), usageInfo.getMaxEdges());
Assert.assertEquals(0, usageInfo.getTransportMessages());
Assert.assertEquals(configuration.getMaxTransportMessages(), usageInfo.getMaxTransportMessages());

View File

@ -50,6 +50,7 @@ import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType;
import org.thingsboard.server.common.data.device.profile.AlarmRule;
import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpec;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
@ -125,7 +126,8 @@ import static org.thingsboard.server.common.data.notification.rule.trigger.confi
@DaoSqlTest
@TestPropertySource(properties = {
"transport.http.enabled=true",
"notification_system.rules.deduplication_durations=RATE_LIMITS:10000"
"notification_system.rules.deduplication_durations=RATE_LIMITS:10000",
"edges.enabled=true"
})
public class NotificationRuleApiTest extends AbstractNotificationApiTest {
@ -372,6 +374,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
profileConfiguration.setMaxUsers(limit);
profileConfiguration.setMaxDashboards(limit);
profileConfiguration.setMaxRuleChains(limit);
profileConfiguration.setMaxEdges(limit);
});
EntitiesLimitNotificationRuleTriggerConfig triggerConfig = EntitiesLimitNotificationRuleTriggerConfig.builder()
@ -419,6 +422,19 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
assertThat(notification.getText()).isEqualTo("Rule chains usage: " + threshold + "/" + limit + " (80%)");
});
checkNotificationAfter(() -> {
for (int i = 1; i <= threshold; i++) {
Edge edge = new Edge();
edge.setName(i + "");
edge.setType("default");
edge.setSecret("secret_" + i);
edge.setRoutingKey("routingKey_" + i);
doPost("/api/edge", edge);
}
}, notification -> {
assertThat(notification.getText()).isEqualTo("Edges usage: " + threshold + "/" + limit + " (80%)");
});
triggerConfig.setThreshold(1.0f);
rule.setTriggerConfig(triggerConfig);
loginSysAdmin();

View File

@ -19,6 +19,7 @@ import lombok.Data;
@Data
public class UsageInfo {
private long devices;
private long maxDevices;
private long assets;
@ -29,6 +30,8 @@ public class UsageInfo {
private long maxUsers;
private long dashboards;
private long maxDashboards;
private long edges;
private long maxEdges;
private long transportMessages;
private long maxTransportMessages;
@ -43,4 +46,5 @@ public class UsageInfo {
private Boolean smsEnabled;
private long alarms;
private long maxAlarms;
}
}

View File

@ -24,6 +24,8 @@ import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.TenantProfileType;
import java.io.Serial;
@Schema
@AllArgsConstructor
@NoArgsConstructor
@ -31,6 +33,7 @@ import org.thingsboard.server.common.data.TenantProfileType;
@Data
public class DefaultTenantProfileConfiguration implements TenantProfileConfiguration {
@Serial
private static final long serialVersionUID = -7134932690332578595L;
private long maxDevices;
@ -39,6 +42,7 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura
private long maxUsers;
private long maxDashboards;
private long maxRuleChains;
private long maxEdges;
private long maxResourcesInBytes;
private long maxOtaPackagesInBytes;
private long maxResourceSize;
@ -131,27 +135,18 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura
@Override
public long getProfileThreshold(ApiUsageRecordKey key) {
switch (key) {
case TRANSPORT_MSG_COUNT:
return maxTransportMessages;
case TRANSPORT_DP_COUNT:
return maxTransportDataPoints;
case JS_EXEC_COUNT:
return maxJSExecutions;
case TBEL_EXEC_COUNT:
return maxTbelExecutions;
case RE_EXEC_COUNT:
return maxREExecutions;
case STORAGE_DP_COUNT:
return maxDPStorageDays;
case EMAIL_EXEC_COUNT:
return maxEmails;
case SMS_EXEC_COUNT:
return maxSms;
case CREATED_ALARMS_COUNT:
return maxCreatedAlarms;
}
return 0L;
return switch (key) {
case TRANSPORT_MSG_COUNT -> maxTransportMessages;
case TRANSPORT_DP_COUNT -> maxTransportDataPoints;
case JS_EXEC_COUNT -> maxJSExecutions;
case TBEL_EXEC_COUNT -> maxTbelExecutions;
case RE_EXEC_COUNT -> maxREExecutions;
case STORAGE_DP_COUNT -> maxDPStorageDays;
case EMAIL_EXEC_COUNT -> maxEmails;
case SMS_EXEC_COUNT -> maxSms;
case CREATED_ALARMS_COUNT -> maxCreatedAlarms;
default -> 0L;
};
}
@Override
@ -170,22 +165,16 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura
}
public long getEntitiesLimit(EntityType entityType) {
switch (entityType) {
case DEVICE:
return maxDevices;
case ASSET:
return maxAssets;
case CUSTOMER:
return maxCustomers;
case USER:
return maxUsers;
case DASHBOARD:
return maxDashboards;
case RULE_CHAIN:
return maxRuleChains;
default:
return 0;
}
return switch (entityType) {
case DEVICE -> maxDevices;
case ASSET -> maxAssets;
case CUSTOMER -> maxCustomers;
case USER -> maxUsers;
case DASHBOARD -> maxDashboards;
case RULE_CHAIN -> maxRuleChains;
case EDGE -> maxEdges;
default -> 0;
};
}
@Override
@ -197,4 +186,5 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura
public int getMaxRuleNodeExecsPerMessage() {
return maxRuleNodeExecutionsPerMessage;
}
}

View File

@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.TenantEntityDao;
import java.util.List;
import java.util.Optional;
@ -34,7 +35,7 @@ import java.util.UUID;
* The Interface EdgeDao.
*
*/
public interface EdgeDao extends Dao<Edge> {
public interface EdgeDao extends Dao<Edge>, TenantEntityDao {
Edge save(TenantId tenantId, Edge edge);

View File

@ -61,6 +61,7 @@ import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.entity.AbstractCachedEntityService;
import org.thingsboard.server.dao.entity.EntityCountService;
import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent;
import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent;
import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent;
@ -127,6 +128,9 @@ public class EdgeServiceImpl extends AbstractCachedEntityService<EdgeCacheKey, E
@Lazy
private RelatedEdgesService relatedEdgesService;
@Autowired
private EntityCountService countService;
@Value("${edges.enabled}")
@Getter
private boolean edgesEnabled;
@ -198,6 +202,9 @@ public class EdgeServiceImpl extends AbstractCachedEntityService<EdgeCacheKey, E
publishEvictEvent(evictEvent);
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(savedEdge.getTenantId())
.entityId(savedEdge.getId()).entity(savedEdge).created(edge.getId() == null).build());
if (edge.getId() == null) {
countService.publishCountEntityEvictEvent(savedEdge.getTenantId(), EntityType.EDGE);
}
return savedEdge;
} catch (Exception t) {
handleEvictEvent(evictEvent);
@ -252,6 +259,7 @@ public class EdgeServiceImpl extends AbstractCachedEntityService<EdgeCacheKey, E
edgeDao.removeById(tenantId, edgeId.getId());
publishEvictEvent(new EdgeCacheEvictEvent(edge.getTenantId(), edge.getName(), null));
countService.publishCountEntityEvictEvent(tenantId, EntityType.EDGE);
eventPublisher.publishEvent(DeleteEntityEvent.builder().tenantId(tenantId).entityId(edgeId).build());
}
@ -604,6 +612,11 @@ public class EdgeServiceImpl extends AbstractCachedEntityService<EdgeCacheKey, E
return result;
}
@Override
public long countByTenantId(TenantId tenantId) {
return edgeDao.countByTenantId(tenantId);
}
@Override
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
return Optional.ofNullable(findEdgeById(tenantId, new EdgeId(entityId.getId())));

View File

@ -18,6 +18,7 @@ package org.thingsboard.server.dao.service.validator;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.CustomerId;
@ -40,6 +41,7 @@ public class EdgeDataValidator extends DataValidator<Edge> {
@Override
protected void validateCreate(TenantId tenantId, Edge edge) {
validateNumberOfEntitiesPerTenant(tenantId, EntityType.EDGE);
}
@Override
@ -76,4 +78,5 @@ public class EdgeDataValidator extends DataValidator<Edge> {
}
}
}
}

View File

@ -143,6 +143,9 @@ public interface EdgeRepository extends JpaRepository<EdgeEntity, UUID> {
@Query("SELECT DISTINCT d.type FROM EdgeEntity d WHERE d.tenantId = :tenantId")
List<String> findTenantEdgeTypes(@Param("tenantId") UUID tenantId);
@Query("SELECT count(*) FROM EdgeEntity e WHERE e.tenantId = :tenantId")
Long countByTenantId(@Param("tenantId") UUID tenantId);
EdgeEntity findByTenantIdAndName(UUID tenantId, String name);
List<EdgeEntity> findEdgesByTenantIdAndCustomerIdAndIdIn(UUID tenantId, UUID customerId, List<UUID> edgeIds);

View File

@ -214,6 +214,11 @@ public class JpaEdgeDao extends JpaAbstractDao<EdgeEntity, Edge> implements Edge
DaoUtil.toPageable(pageLink)));
}
@Override
public Long countByTenantId(TenantId tenantId) {
return edgeRepository.countByTenantId(tenantId.getId());
}
@Override
public EntityType getEntityType() {
return EntityType.EDGE;

View File

@ -63,6 +63,8 @@ public class BasicUsageInfoService implements UsageInfoService {
usageInfo.setMaxUsers(profileConfiguration.getMaxUsers());
usageInfo.setDashboards(countService.countByTenantIdAndEntityType(tenantId, EntityType.DASHBOARD));
usageInfo.setMaxDashboards(profileConfiguration.getMaxDashboards());
usageInfo.setEdges(countService.countByTenantIdAndEntityType(tenantId, EntityType.EDGE));
usageInfo.setMaxEdges(profileConfiguration.getMaxEdges());
usageInfo.setMaxAlarms(profileConfiguration.getMaxCreatedAlarms());
usageInfo.setMaxTransportMessages(profileConfiguration.getMaxTransportMessages());

View File

@ -17,6 +17,7 @@ package org.thingsboard.server.dao.service;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
@ -26,6 +27,7 @@ import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
@ -35,10 +37,13 @@ import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantProfileService;
import java.util.ArrayList;
import java.util.Arrays;
@ -51,6 +56,10 @@ import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@DaoSqlTest
public class EdgeServiceTest extends AbstractServiceTest {
@Autowired
TbTenantProfileCache tbTenantProfileCache;
@Autowired
TenantProfileService tenantProfileService;
@Autowired
CustomerService customerService;
@Autowired
@ -58,7 +67,13 @@ public class EdgeServiceTest extends AbstractServiceTest {
@Autowired
RuleChainService ruleChainService;
private IdComparator<Edge> idComparator = new IdComparator<>();
private final IdComparator<Edge> idComparator = new IdComparator<>();
@After
public void after() {
tenantService.deleteTenant(tenantId);
tenantProfileService.deleteTenantProfiles(tenantId);
}
@Test
public void testSaveEdge() {
@ -113,6 +128,34 @@ public class EdgeServiceTest extends AbstractServiceTest {
});
}
@Test
public void testSaveEdgesWithInfiniteMaxEdgeLimit() {
TenantProfile defaultTenantProfile = tenantProfileService.findDefaultTenantProfile(tenantId);
defaultTenantProfile.getProfileData().setConfiguration(DefaultTenantProfileConfiguration.builder().maxEdges(Long.MAX_VALUE).build());
tenantProfileService.saveTenantProfile(tenantId, defaultTenantProfile);
Edge savedEdge = edgeService.saveEdge(constructEdge("My edge", "default"));
edgeService.deleteEdge(tenantId, savedEdge.getId());
}
@Test
public void testSaveEdgesWithMaxEdgeOutOfLimit() {
TenantProfile defaultTenantProfile = tenantProfileService.findDefaultTenantProfile(tenantId);
defaultTenantProfile.getProfileData().setConfiguration(DefaultTenantProfileConfiguration.builder().maxEdges(1).build());
tenantProfileService.saveTenantProfile(tenantId, defaultTenantProfile);
tbTenantProfileCache.evict(defaultTenantProfile.getId());
Assert.assertEquals(0, edgeService.countByTenantId(tenantId));
Edge savedEdge = edgeService.saveEdge(constructEdge("My first edge", "default"));
Assert.assertEquals(1, edgeService.countByTenantId(tenantId));
Assertions.assertThrows(DataValidationException.class, () -> {
edgeService.saveEdge(constructEdge("My second edge that out of maxEdgeCount limit", "default"));
});
edgeService.deleteEdge(tenantId, savedEdge.getId());
}
@Test
public void testAssignEdgeToNonExistentCustomer() {
Edge edge = constructEdge("My edge", "default");
@ -668,5 +711,8 @@ public class EdgeServiceTest extends AbstractServiceTest {
PageData<Edge> edgesPageData = edgeService.findEdgesByTenantProfileId(tenant2.getTenantProfileId(),
new PageLink(1000));
Assert.assertEquals(2, edgesPageData.getTotalElements());
tenantService.deleteTenant(tenant1.getId());
tenantService.deleteTenant(tenant2.getId());
}
}

View File

@ -111,6 +111,22 @@
<mat-hint></mat-hint>
</mat-form-field>
</div>
<div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4">
<mat-form-field class="mat-block flex-1" appearance="fill">
<mat-label translate>tenant-profile.maximum-edges</mat-label>
<input matInput required min="0" step="1"
formControlName="maxEdges"
type="number">
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxEdges').hasError('required')">
{{ 'tenant-profile.maximum-edges-required' | translate }}
</mat-error>
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxEdges').hasError('min')">
{{ 'tenant-profile.maximum-edges-range' | translate }}
</mat-error>
<mat-hint></mat-hint>
</mat-form-field>
<div class="flex-1"></div>
</div>
</ng-template>
</mat-expansion-panel>
</fieldset>

View File

@ -65,6 +65,7 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA
maxUsers: [null, [Validators.required, Validators.min(0)]],
maxDashboards: [null, [Validators.required, Validators.min(0)]],
maxRuleChains: [null, [Validators.required, Validators.min(0)]],
maxEdges: [null, [Validators.required, Validators.min(0)]],
maxResourcesInBytes: [null, [Validators.required, Validators.min(0)]],
maxOtaPackagesInBytes: [null, [Validators.required, Validators.min(0)]],
maxResourceSize: [null, [Validators.required, Validators.min(0)]],

View File

@ -5146,6 +5146,9 @@
"maximum-dashboards": "الحد الأقصى لعدد لوحات التحكم",
"maximum-dashboards-required": "الحد الأقصى لعدد لوحات التحكم مطلوب.",
"maximum-dashboards-range": "لا يمكن أن يكون الحد الأقصى لعدد لوحات التحكم سالبًا",
"maximum-edges": "الحد الأقصى لعدد الحواف",
"maximum-edges-required": "يجب أن يكون الحد الأقصى لعدد الحواف.",
"maximum-edges-range": "لا يمكن أن يكون الحد الأقصى لعدد الحواف سالبًا",
"maximum-rule-chains": "الحد الأقصى لعدد سلاسل القواعد",
"maximum-rule-chains-required": "الحد الأقصى لعدد سلاسل القواعد مطلوب.",
"maximum-rule-chains-range": "لا يمكن أن يكون الحد الأقصى لعدد سلاسل القواعد سالبًا",

View File

@ -4142,6 +4142,9 @@
"maximum-dashboards": "Nº Màxim de panells (0 - sense límit)",
"maximum-dashboards-required": "Cal Nº Màxim de panells.",
"maximum-dashboards-range": "Nº Màxim de panells no pot ser negatiu",
"maximum-edges": "Nº Màxim de d'arestes (0 - sense límit)",
"maximum-edges-required": "Cal Nº Màxim de d'arestes.",
"maximum-edges-range": "Nº Màxim de d'arestes no pot ser negatiu",
"maximum-rule-chains": "Nº Màxim de cadenes de regles (0 - sense límit)",
"maximum-rule-chains-required": "Cal Nº Màxim de cadenes de regles.",
"maximum-rule-chains-range": "Nº Màxim de cadenes de regles no pot ser negatiu",

View File

@ -2522,6 +2522,9 @@
"maximum-dashboards": "Maximální počet dashboardů (0 - neomezeno)",
"maximum-dashboards-required": "Maximální počet dashboardů je povinný.",
"maximum-dashboards-range": "Maximální počet dashboardů nemůže být záporný",
"maximum-edges": "Maximální počet hran (0 - neomezeno)",
"maximum-edges-required": "Maximální počet hran je povinný.",
"maximum-edges-range": "Maximální počet hran nemůže být záporný",
"maximum-rule-chains": "Maximální počet řetězů pravidel (0 - neomezeno)",
"maximum-rule-chains-required": "Maximální počet řetězů pravidel je povinný.",
"maximum-rule-chains-range": "Maximální počet řetězů pravidel nemůže být záporný",

View File

@ -3038,6 +3038,9 @@
"maximum-dashboards": "Maks. antal dashboards (0 ubegrænset)",
"maximum-dashboards-required": "Maks. antal dashboards er påkrævet.",
"maximum-dashboards-range": "Maks. antal dashboards kan ikke være negativt",
"maximum-edges": "Maks. antal edges (0 ubegrænset)",
"maximum-edges-required": "Maks. antal edges er påkrævet.",
"maximum-edges-range": "Maks. antal edges kan ikke være negativt",
"maximum-rule-chains": "Maks. antal regelkæder (0 ubegrænset)",
"maximum-rule-chains-required": "Maks. antal regelkæder er påkrævet.",
"maximum-rule-chains-range": "Maks. antal regelkæder kan ikke være negativt",

View File

@ -4447,6 +4447,9 @@
"maximum-dashboards": "Dashboards maximum number",
"maximum-dashboards-required": "Dashboards maximum number is required.",
"maximum-dashboards-range": "Dashboards maximum number can't be negative",
"maximum-edges": "Edges maximum number",
"maximum-edges-required": "Edges maximum number is required.",
"maximum-edges-range": "Edges maximum number can't be negative",
"maximum-rule-chains": "Rule chains maximum number",
"maximum-rule-chains-required": "Rule chains maximum number is required.",
"maximum-rule-chains-range": "Rule chains maximum number can't be negative",

View File

@ -3739,6 +3739,9 @@
"maximum-dashboards": "Nº Máximo de paneles (0 - sin límite)",
"maximum-dashboards-required": "Nº Máximo de paneles requerido.",
"maximum-dashboards-range": "Nº Máximo de paneles no puede ser negativo",
"maximum-edges": "Nº Máximo de bordes (0 - sin límite)",
"maximum-edges-required": "Nº Máximo de bordes requerido.",
"maximum-edges-range": "Nº Máximo de bordes no puede ser negativo",
"maximum-rule-chains": "Nº Máximo de cadenas de reglas (0 - sin límite)",
"maximum-rule-chains-required": "Nº Máximo de cadenas de reglas requerido.",
"maximum-rule-chains-range": "Nº Máximo de cadenas de reglas no puede ser negativo",

View File

@ -1967,6 +1967,9 @@
"maximum-dashboards": "Maximum number of dashboards (0 - unlimited)",
"maximum-dashboards-required": "Maximum number of dashboards is required.",
"maximum-dashboards-range": "Maximum number of dashboards can't be negative",
"maximum-edges": "Edges maximum number (0 - unlimited)",
"maximum-edges-required": "Edges maximum number is required.",
"maximum-edges-range": "Edges maximum number can't be negative",
"maximum-rule-chains": "Maximum number of rule chains (0 - unlimited)",
"maximum-rule-chains-required": "Maximum number of rule chains is required.",
"maximum-rule-chains-range": "Maximum number of rule chains can't be negative",

View File

@ -5042,6 +5042,9 @@
"maximum-dashboards": "Dashboards maximum number",
"maximum-dashboards-required": "Dashboards maximum number is required.",
"maximum-dashboards-range": "Dashboards maximum number can't be negative",
"maximum-edges": "Edges maximum number",
"maximum-edges-required": "Edges maximum number is required.",
"maximum-edges-range": "Edges maximum number can't be negative",
"maximum-rule-chains": "Rule chains maximum number",
"maximum-rule-chains-required": "Rule chains maximum number is required.",
"maximum-rule-chains-range": "Rule chains maximum number can't be negative",

View File

@ -4841,6 +4841,9 @@
"maximum-dashboards": "Dashboards maximaal aantal",
"maximum-dashboards-required": "Het maximale aantal dashboards is vereist.",
"maximum-dashboards-range": "Het maximumaantal dashboards mag niet negatief zijn",
"maximum-edges": "Randen maximaal aantal",
"maximum-edges-required": "Het maximale aantal randen is vereist.",
"maximum-edges-range": "Het maximumaantal randen mag niet negatief zijn",
"maximum-rule-chains": "Maximaal aantal rule chains",
"maximum-rule-chains-required": "Het maximale aantal rule chains is vereist.",
"maximum-rule-chains-range": "Het maximale aantal rule chains mag niet negatief zijn",

View File

@ -5059,6 +5059,9 @@
"maximum-dashboards": "Maksymalna liczba paneli",
"maximum-dashboards-required": "Maksymalna liczba paneli jest wymagana.",
"maximum-dashboards-range": "Maksymalna liczba paneli nie może być ujemna",
"maximum-edges": "Maksymalna liczba krawędzi",
"maximum-edges-required": "Maksymalna liczba krawędzi jest wymagana.",
"maximum-edges-range": "Maksymalna liczba krawędzi nie może być ujemna",
"maximum-rule-chains": "Maksymalna liczba łańcuchów reguł",
"maximum-rule-chains-required": "Maksymalna liczba łańcuchów reguł jest wymagana.",
"maximum-rule-chains-range": "Maksymalna liczba łańcuchów reguł nie może być ujemna",

View File

@ -1968,6 +1968,9 @@
"maximum-dashboards": "Maximum number of dashboards (0 - unlimited)",
"maximum-dashboards-required": "Maximum number of dashboards is required.",
"maximum-dashboards-range": "Maximum number of dashboards can't be negative",
"maximum-edges": "Edges maximum number (0 - unlimited)",
"maximum-edges-required": "Edges maximum number is required.",
"maximum-edges-range": "Edges maximum number can't be negative",
"maximum-rule-chains": "Maximum number of rule chains (0 - unlimited)",
"maximum-rule-chains-required": "Maximum number of rule chains is required.",
"maximum-rule-chains-range": "Maximum number of rule chains can't be negative",

View File

@ -2543,6 +2543,9 @@
"maximum-dashboards": "Maksimum gösterge paneli sayısı (0 - sınırsız)",
"maximum-dashboards-required": "Maksimum gösterge paneli sayısı gerekli.",
"maximum-dashboards-range": "Maksimum gösterge paneli sayısı negatif olamaz",
"maximum-edges": "Maksimum gösterge kenar sayısı (0 - sınırsız)",
"maximum-edges-required": "Maksimum gösterge kenar sayısı gerekli.",
"maximum-edges-range": "Maksimum gösterge kenar sayısı negatif olamaz",
"maximum-rule-chains": "Maksimum kural zinciri sayısı (0 - sınırsız)",
"maximum-rule-chains-required": "Maksimum kural zinciri sayısı gerekli.",
"maximum-rule-chains-range": "Maksimum kural zinciri sayısı negatif olamaz",

View File

@ -4234,6 +4234,9 @@
"maximum-dashboards": "最大仪表板数",
"maximum-dashboards-required": "最大仪表板数必填。",
"maximum-dashboards-range": "最大仪表板数不能为负数",
"maximum-edges": "最大边数",
"maximum-edges-required": "需要最大边数。",
"maximum-edges-range": "边的最大数量不能为负数",
"maximum-rule-chains": "最大规则链数",
"maximum-rule-chains-required": "最大规则链数必填。",
"maximum-rule-chains-range": "最大规则链数不能为负数",

View File

@ -2972,6 +2972,9 @@
"maximum-dashboards": "儀表板最大數量",
"maximum-dashboards-required": "儀表板最大數量必填",
"maximum-dashboards-range": "儀表板最大數量不可為否",
"maximum-edges": "最大邊數",
"maximum-edges-required": "需要最大邊數。",
"maximum-edges-range": "邊數最大不能為負數",
"maximum-rule-chains": "規則鏈最大數量",
"maximum-rule-chains-required": "規則鏈最大數量必填",
"maximum-rule-chains-range": "規則鏈最大數量不可為否",