Merge pull request #13301 from yuliaklochai/ui-trendz-settings
Added Trendz setting
This commit is contained in:
		
						commit
						0e20e27f6f
					
				@ -42,6 +42,7 @@ import org.thingsboard.server.common.data.settings.UserSettings;
 | 
			
		||||
import org.thingsboard.server.common.data.settings.UserSettingsType;
 | 
			
		||||
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
 | 
			
		||||
import org.thingsboard.server.dao.mobile.QrCodeSettingService;
 | 
			
		||||
import org.thingsboard.server.dao.trendz.TrendzSettingsService;
 | 
			
		||||
import org.thingsboard.server.queue.util.TbCoreComponent;
 | 
			
		||||
import org.thingsboard.server.service.security.model.SecurityUser;
 | 
			
		||||
import org.thingsboard.server.service.security.model.UserPrincipal;
 | 
			
		||||
@ -87,6 +88,9 @@ public class SystemInfoController extends BaseController {
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private DebugModeRateLimitsConfig debugModeRateLimitsConfig;
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private TrendzSettingsService trendzSettingsService;
 | 
			
		||||
 | 
			
		||||
    @PostConstruct
 | 
			
		||||
    public void init() {
 | 
			
		||||
        JsonNode info = buildInfoObject();
 | 
			
		||||
@ -158,6 +162,7 @@ public class SystemInfoController extends BaseController {
 | 
			
		||||
            }
 | 
			
		||||
            systemParams.setMaxArgumentsPerCF(tenantProfileConfiguration.getMaxArgumentsPerCF());
 | 
			
		||||
            systemParams.setMaxDataPointsPerRollingArg(tenantProfileConfiguration.getMaxDataPointsPerRollingArg());
 | 
			
		||||
            systemParams.setTrendzSettings(trendzSettingsService.findTrendzSettings(currentUser.getTenantId()));
 | 
			
		||||
        }
 | 
			
		||||
        systemParams.setMobileQrEnabled(Optional.ofNullable(qrCodeSettingService.findQrCodeSettings(TenantId.SYS_TENANT_ID))
 | 
			
		||||
                .map(QrCodeSettings::getQrCodeConfig).map(QRCodeConfig::isShowOnHomePage)
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,80 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright © 2016-2025 The Thingsboard Authors
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
package org.thingsboard.server.controller;
 | 
			
		||||
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import org.springframework.security.access.prepost.PreAuthorize;
 | 
			
		||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
 | 
			
		||||
import org.springframework.web.bind.annotation.GetMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.PostMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestBody;
 | 
			
		||||
import org.springframework.web.bind.annotation.RequestMapping;
 | 
			
		||||
import org.springframework.web.bind.annotation.RestController;
 | 
			
		||||
import org.thingsboard.server.common.data.exception.ThingsboardException;
 | 
			
		||||
import org.thingsboard.server.common.data.id.TenantId;
 | 
			
		||||
import org.thingsboard.server.common.data.trendz.TrendzSettings;
 | 
			
		||||
import org.thingsboard.server.config.annotations.ApiOperation;
 | 
			
		||||
import org.thingsboard.server.dao.trendz.TrendzSettingsService;
 | 
			
		||||
import org.thingsboard.server.queue.util.TbCoreComponent;
 | 
			
		||||
import org.thingsboard.server.service.security.model.SecurityUser;
 | 
			
		||||
import org.thingsboard.server.service.security.permission.Operation;
 | 
			
		||||
import org.thingsboard.server.service.security.permission.Resource;
 | 
			
		||||
 | 
			
		||||
import static org.thingsboard.server.controller.ControllerConstants.MARKDOWN_CODE_BLOCK_END;
 | 
			
		||||
import static org.thingsboard.server.controller.ControllerConstants.MARKDOWN_CODE_BLOCK_START;
 | 
			
		||||
import static org.thingsboard.server.controller.ControllerConstants.NEW_LINE;
 | 
			
		||||
import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH;
 | 
			
		||||
import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH;
 | 
			
		||||
 | 
			
		||||
@RestController
 | 
			
		||||
@TbCoreComponent
 | 
			
		||||
@RequiredArgsConstructor
 | 
			
		||||
@RequestMapping("/api")
 | 
			
		||||
public class TrendzController extends BaseController {
 | 
			
		||||
 | 
			
		||||
    private final TrendzSettingsService trendzSettingsService;
 | 
			
		||||
 | 
			
		||||
    @ApiOperation(value = "Save Trendz settings (saveTrendzSettings)",
 | 
			
		||||
            notes = "Saves Trendz settings for this tenant.\n" + NEW_LINE +
 | 
			
		||||
                    "Here is an example of the Trendz settings:\n" +
 | 
			
		||||
                    MARKDOWN_CODE_BLOCK_START +
 | 
			
		||||
                    "{\n" +
 | 
			
		||||
                    "  \"enabled\": true,\n" +
 | 
			
		||||
                    "  \"baseUrl\": \"https://some.domain.com:18888/also_necessary_prefix\"\n" +
 | 
			
		||||
                    "}" +
 | 
			
		||||
                    MARKDOWN_CODE_BLOCK_END +
 | 
			
		||||
                    TENANT_AUTHORITY_PARAGRAPH)
 | 
			
		||||
    @PostMapping("/trendz/settings")
 | 
			
		||||
    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
 | 
			
		||||
    public TrendzSettings saveTrendzSettings(@RequestBody TrendzSettings trendzSettings,
 | 
			
		||||
                                             @AuthenticationPrincipal SecurityUser user) throws ThingsboardException {
 | 
			
		||||
        accessControlService.checkPermission(user, Resource.ADMIN_SETTINGS, Operation.WRITE);
 | 
			
		||||
        TenantId tenantId = user.getTenantId();
 | 
			
		||||
        trendzSettingsService.saveTrendzSettings(tenantId, trendzSettings);
 | 
			
		||||
        return trendzSettings;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @ApiOperation(value = "Get Trendz Settings (getTrendzSettings)",
 | 
			
		||||
            notes = "Retrieves Trendz settings for this tenant." +
 | 
			
		||||
                    TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
 | 
			
		||||
    @GetMapping("/trendz/settings")
 | 
			
		||||
    @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
 | 
			
		||||
    public TrendzSettings getTrendzSettings(@AuthenticationPrincipal SecurityUser user) {
 | 
			
		||||
        TenantId tenantId = user.getTenantId();
 | 
			
		||||
        return trendzSettingsService.findTrendzSettings(tenantId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -23,7 +23,7 @@ import org.thingsboard.server.common.data.id.UserId;
 | 
			
		||||
import org.thingsboard.server.common.data.security.Authority;
 | 
			
		||||
import org.thingsboard.server.service.security.model.SecurityUser;
 | 
			
		||||
 | 
			
		||||
@Component(value="sysAdminPermissions")
 | 
			
		||||
@Component(value = "sysAdminPermissions")
 | 
			
		||||
public class SysAdminPermissions extends AbstractPermissions {
 | 
			
		||||
 | 
			
		||||
    public SysAdminPermissions() {
 | 
			
		||||
 | 
			
		||||
@ -644,6 +644,9 @@ cache:
 | 
			
		||||
    mobileSecretKey:
 | 
			
		||||
      timeToLiveInMinutes: "${CACHE_MOBILE_SECRET_KEY_TTL:2}" # QR secret key cache TTL
 | 
			
		||||
      maxSize: "${CACHE_MOBILE_SECRET_KEY_MAX_SIZE:10000}" # 0 means the cache is disabled
 | 
			
		||||
    trendzSettings:
 | 
			
		||||
      timeToLiveInMinutes: "${CACHE_SPECS_TRENDZ_SETTINGS_TTL:1440}" # Trendz settings cache TTL
 | 
			
		||||
      maxSize: "${CACHE_SPECS_TRENDZ_SETTINGS_MAX_SIZE:10000}" # 0 means the cache is disabled
 | 
			
		||||
 | 
			
		||||
  # Deliberately placed outside the 'specs' group above
 | 
			
		||||
  notificationRules:
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,77 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright © 2016-2025 The Thingsboard Authors
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
package org.thingsboard.server.controller;
 | 
			
		||||
 | 
			
		||||
import org.junit.Before;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
import org.thingsboard.server.common.data.trendz.TrendzSettings;
 | 
			
		||||
import org.thingsboard.server.dao.service.DaoSqlTest;
 | 
			
		||||
 | 
			
		||||
import static org.assertj.core.api.Assertions.assertThat;
 | 
			
		||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 | 
			
		||||
 | 
			
		||||
@DaoSqlTest
 | 
			
		||||
public class TrendzControllerTest extends AbstractControllerTest {
 | 
			
		||||
 | 
			
		||||
    private final String trendzUrl = "https://some.domain.com:18888/also_necessary_prefix";
 | 
			
		||||
 | 
			
		||||
    @Before
 | 
			
		||||
    public void setUp() throws Exception {
 | 
			
		||||
        loginTenantAdmin();
 | 
			
		||||
 | 
			
		||||
        TrendzSettings trendzSettings = new TrendzSettings();
 | 
			
		||||
        trendzSettings.setEnabled(true);
 | 
			
		||||
        trendzSettings.setBaseUrl(trendzUrl);
 | 
			
		||||
 | 
			
		||||
        doPost("/api/trendz/settings", trendzSettings).andExpect(status().isOk());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testTrendzSettingsWhenTenant() throws Exception {
 | 
			
		||||
        loginTenantAdmin();
 | 
			
		||||
 | 
			
		||||
        TrendzSettings trendzSettings = doGet("/api/trendz/settings", TrendzSettings.class);
 | 
			
		||||
 | 
			
		||||
        assertThat(trendzSettings).isNotNull();
 | 
			
		||||
        assertThat(trendzSettings.isEnabled()).isTrue();
 | 
			
		||||
        assertThat(trendzSettings.getBaseUrl()).isEqualTo(trendzUrl);
 | 
			
		||||
 | 
			
		||||
        String updatedUrl = "https://some.domain.com:18888/tenant_trendz";
 | 
			
		||||
        trendzSettings.setBaseUrl(updatedUrl);
 | 
			
		||||
 | 
			
		||||
        doPost("/api/trendz/settings", trendzSettings).andExpect(status().isOk());
 | 
			
		||||
 | 
			
		||||
        TrendzSettings updatedTrendzSettings = doGet("/api/trendz/settings", TrendzSettings.class);
 | 
			
		||||
        assertThat(updatedTrendzSettings).isEqualTo(trendzSettings);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testTrendzSettingsWhenCustomer() throws Exception {
 | 
			
		||||
        loginCustomerUser();
 | 
			
		||||
 | 
			
		||||
        TrendzSettings newTrendzSettings = new TrendzSettings();
 | 
			
		||||
        newTrendzSettings.setEnabled(true);
 | 
			
		||||
        newTrendzSettings.setBaseUrl("https://some.domain.com:18888/customer_trendz");
 | 
			
		||||
 | 
			
		||||
        doPost("/api/trendz/settings", newTrendzSettings).andExpect(status().isForbidden());
 | 
			
		||||
 | 
			
		||||
        TrendzSettings fetchedTrendzSettings = doGet("/api/trendz/settings", TrendzSettings.class);
 | 
			
		||||
        assertThat(fetchedTrendzSettings).isNotNull();
 | 
			
		||||
        assertThat(fetchedTrendzSettings.isEnabled()).isTrue();
 | 
			
		||||
        assertThat(fetchedTrendzSettings.getBaseUrl()).isEqualTo(trendzUrl);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,29 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright © 2016-2025 The Thingsboard Authors
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
package org.thingsboard.server.dao.trendz;
 | 
			
		||||
 | 
			
		||||
import org.thingsboard.server.common.data.id.TenantId;
 | 
			
		||||
import org.thingsboard.server.common.data.trendz.TrendzSettings;
 | 
			
		||||
 | 
			
		||||
public interface TrendzSettingsService {
 | 
			
		||||
 | 
			
		||||
    void saveTrendzSettings(TenantId tenantId, TrendzSettings settings);
 | 
			
		||||
 | 
			
		||||
    TrendzSettings findTrendzSettings(TenantId tenantId);
 | 
			
		||||
 | 
			
		||||
    void deleteTrendzSettings(TenantId tenantId);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -35,6 +35,7 @@ public class CacheConstants {
 | 
			
		||||
    public static final String DEVICE_PROFILE_CACHE = "deviceProfiles";
 | 
			
		||||
    public static final String NOTIFICATION_SETTINGS_CACHE = "notificationSettings";
 | 
			
		||||
    public static final String SENT_NOTIFICATIONS_CACHE = "sentNotifications";
 | 
			
		||||
    public static final String TRENDZ_SETTINGS_CACHE = "trendzSettings";
 | 
			
		||||
 | 
			
		||||
    public static final String ASSET_PROFILE_CACHE = "assetProfiles";
 | 
			
		||||
    public static final String ATTRIBUTES_CACHE = "attributes";
 | 
			
		||||
 | 
			
		||||
@ -17,6 +17,7 @@ package org.thingsboard.server.common.data;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.databind.JsonNode;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import org.thingsboard.server.common.data.trendz.TrendzSettings;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
@ -37,4 +38,5 @@ public class SystemParams {
 | 
			
		||||
    String calculatedFieldDebugPerTenantLimitsConfiguration;
 | 
			
		||||
    long maxArgumentsPerCF;
 | 
			
		||||
    long maxDataPointsPerRollingArg;
 | 
			
		||||
    TrendzSettings trendzSettings;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,26 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright © 2016-2025 The Thingsboard Authors
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
package org.thingsboard.server.common.data.trendz;
 | 
			
		||||
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
public class TrendzSettings {
 | 
			
		||||
 | 
			
		||||
    private boolean enabled;
 | 
			
		||||
    private String baseUrl;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -44,6 +44,7 @@ import org.thingsboard.server.dao.service.PaginatedRemover;
 | 
			
		||||
import org.thingsboard.server.dao.service.Validator;
 | 
			
		||||
import org.thingsboard.server.dao.service.validator.TenantDataValidator;
 | 
			
		||||
import org.thingsboard.server.dao.settings.AdminSettingsService;
 | 
			
		||||
import org.thingsboard.server.dao.trendz.TrendzSettingsService;
 | 
			
		||||
import org.thingsboard.server.dao.usagerecord.ApiUsageStateService;
 | 
			
		||||
import org.thingsboard.server.dao.user.UserService;
 | 
			
		||||
 | 
			
		||||
@ -81,6 +82,8 @@ public class TenantServiceImpl extends AbstractCachedEntityService<TenantId, Ten
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private QrCodeSettingService qrCodeSettingService;
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private TrendzSettingsService trendzSettingsService;
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private TenantDataValidator tenantValidator;
 | 
			
		||||
    @Autowired
 | 
			
		||||
    protected TbTransactionalCache<TenantId, Boolean> existsTenantCache;
 | 
			
		||||
@ -163,9 +166,10 @@ public class TenantServiceImpl extends AbstractCachedEntityService<TenantId, Ten
 | 
			
		||||
        Validator.validateId(tenantId, id -> INCORRECT_TENANT_ID + id);
 | 
			
		||||
 | 
			
		||||
        userService.deleteAllByTenantId(tenantId);
 | 
			
		||||
        notificationSettingsService.deleteNotificationSettings(tenantId);
 | 
			
		||||
        trendzSettingsService.deleteTrendzSettings(tenantId);
 | 
			
		||||
        adminSettingsService.deleteAdminSettingsByTenantId(tenantId);
 | 
			
		||||
        qrCodeSettingService.deleteByTenantId(tenantId);
 | 
			
		||||
        notificationSettingsService.deleteNotificationSettings(tenantId);
 | 
			
		||||
 | 
			
		||||
        tenantDao.removeById(tenantId, tenantId.getId());
 | 
			
		||||
        publishEvictEvent(new TenantEvictEvent(tenantId, true));
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,69 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright © 2016-2025 The Thingsboard Authors
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
package org.thingsboard.server.dao.trendz;
 | 
			
		||||
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.springframework.cache.annotation.CacheEvict;
 | 
			
		||||
import org.springframework.cache.annotation.Cacheable;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
import org.thingsboard.common.util.JacksonUtil;
 | 
			
		||||
import org.thingsboard.server.common.data.AdminSettings;
 | 
			
		||||
import org.thingsboard.server.common.data.CacheConstants;
 | 
			
		||||
import org.thingsboard.server.common.data.id.TenantId;
 | 
			
		||||
import org.thingsboard.server.common.data.trendz.TrendzSettings;
 | 
			
		||||
import org.thingsboard.server.dao.settings.AdminSettingsService;
 | 
			
		||||
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
 | 
			
		||||
@Service
 | 
			
		||||
@RequiredArgsConstructor
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class DefaultTrendzSettingsService implements TrendzSettingsService {
 | 
			
		||||
 | 
			
		||||
    private final AdminSettingsService adminSettingsService;
 | 
			
		||||
 | 
			
		||||
    private static final String SETTINGS_KEY = "trendz";
 | 
			
		||||
 | 
			
		||||
    @CacheEvict(cacheNames = CacheConstants.TRENDZ_SETTINGS_CACHE, key = "#tenantId")
 | 
			
		||||
    @Override
 | 
			
		||||
    public void saveTrendzSettings(TenantId tenantId, TrendzSettings settings) {
 | 
			
		||||
        AdminSettings adminSettings = Optional.ofNullable(adminSettingsService.findAdminSettingsByTenantIdAndKey(tenantId, SETTINGS_KEY))
 | 
			
		||||
                .orElseGet(() -> {
 | 
			
		||||
                    AdminSettings newAdminSettings = new AdminSettings();
 | 
			
		||||
                    newAdminSettings.setTenantId(tenantId);
 | 
			
		||||
                    newAdminSettings.setKey(SETTINGS_KEY);
 | 
			
		||||
                    return newAdminSettings;
 | 
			
		||||
                });
 | 
			
		||||
        adminSettings.setJsonValue(JacksonUtil.valueToTree(settings));
 | 
			
		||||
        adminSettingsService.saveAdminSettings(tenantId, adminSettings);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Cacheable(cacheNames = CacheConstants.TRENDZ_SETTINGS_CACHE, key = "#tenantId")
 | 
			
		||||
    @Override
 | 
			
		||||
    public TrendzSettings findTrendzSettings(TenantId tenantId) {
 | 
			
		||||
        return Optional.ofNullable(adminSettingsService.findAdminSettingsByTenantIdAndKey(tenantId, SETTINGS_KEY))
 | 
			
		||||
                .map(adminSettings -> JacksonUtil.treeToValue(adminSettings.getJsonValue(), TrendzSettings.class))
 | 
			
		||||
                .orElseGet(TrendzSettings::new);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @CacheEvict(cacheNames = CacheConstants.TRENDZ_SETTINGS_CACHE, key = "#tenantId")
 | 
			
		||||
    @Override
 | 
			
		||||
    public void deleteTrendzSettings(TenantId tenantId) {
 | 
			
		||||
        adminSettingsService.deleteAdminSettingsByTenantIdAndKey(tenantId, SETTINGS_KEY);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -108,6 +108,9 @@ cache.specs.qrCodeSettings.maxSize=10000
 | 
			
		||||
cache.specs.mobileSecretKey.timeToLiveInMinutes=1440
 | 
			
		||||
cache.specs.mobileSecretKey.maxSize=10000
 | 
			
		||||
 | 
			
		||||
cache.specs.trendzSettings.timeToLiveInMinutes=1440
 | 
			
		||||
cache.specs.trendzSettings.maxSize=10000
 | 
			
		||||
 | 
			
		||||
redis.connection.host=localhost
 | 
			
		||||
redis.connection.port=6379
 | 
			
		||||
redis.connection.db=0
 | 
			
		||||
 | 
			
		||||
@ -16,6 +16,7 @@
 | 
			
		||||
 | 
			
		||||
import { AuthUser, User } from '@shared/models/user.model';
 | 
			
		||||
import { UserSettings } from '@shared/models/user-settings.models';
 | 
			
		||||
import { TrendzSettings } from '@shared/models/trendz-settings.models';
 | 
			
		||||
 | 
			
		||||
export interface SysParamsState {
 | 
			
		||||
  userTokenAccessEnabled: boolean;
 | 
			
		||||
@ -32,6 +33,7 @@ export interface SysParamsState {
 | 
			
		||||
  maxArgumentsPerCF: number;
 | 
			
		||||
  ruleChainDebugPerTenantLimitsConfiguration?: string;
 | 
			
		||||
  calculatedFieldDebugPerTenantLimitsConfiguration?: string;
 | 
			
		||||
  trendzSettings: TrendzSettings;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface SysParams extends SysParamsState {
 | 
			
		||||
 | 
			
		||||
@ -17,6 +17,7 @@
 | 
			
		||||
import { AuthPayload, AuthState } from './auth.models';
 | 
			
		||||
import { AuthActions, AuthActionTypes } from './auth.actions';
 | 
			
		||||
import { initialUserSettings, UserSettings } from '@shared/models/user-settings.models';
 | 
			
		||||
import { initialTrendzSettings } from '@shared/models/trendz-settings.models';
 | 
			
		||||
import { unset } from '@core/utils';
 | 
			
		||||
 | 
			
		||||
const emptyUserAuthState: AuthPayload = {
 | 
			
		||||
@ -34,7 +35,8 @@ const emptyUserAuthState: AuthPayload = {
 | 
			
		||||
  maxArgumentsPerCF: 0,
 | 
			
		||||
  maxDataPointsPerRollingArg: 0,
 | 
			
		||||
  maxDebugModeDurationMinutes: 0,
 | 
			
		||||
  userSettings: initialUserSettings
 | 
			
		||||
  userSettings: initialUserSettings,
 | 
			
		||||
  trendzSettings: initialTrendzSettings
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const initialState: AuthState = {
 | 
			
		||||
 | 
			
		||||
@ -47,3 +47,4 @@ export * from './user.service';
 | 
			
		||||
export * from './user-settings.service';
 | 
			
		||||
export * from './widget.service';
 | 
			
		||||
export * from './usage-info.service';
 | 
			
		||||
export * from './trendz-settings.service'
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										39
									
								
								ui-ngx/src/app/core/http/trendz-settings.service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								ui-ngx/src/app/core/http/trendz-settings.service.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,39 @@
 | 
			
		||||
///
 | 
			
		||||
/// Copyright © 2016-2025 The Thingsboard Authors
 | 
			
		||||
///
 | 
			
		||||
/// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
/// you may not use this file except in compliance with the License.
 | 
			
		||||
/// You may obtain a copy of the License at
 | 
			
		||||
///
 | 
			
		||||
///     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
///
 | 
			
		||||
/// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
/// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
/// See the License for the specific language governing permissions and
 | 
			
		||||
/// limitations under the License.
 | 
			
		||||
///
 | 
			
		||||
 | 
			
		||||
import { Injectable } from '@angular/core';
 | 
			
		||||
import { Observable } from 'rxjs';
 | 
			
		||||
import { HttpClient } from '@angular/common/http';
 | 
			
		||||
import { TrendzSettings } from '@shared/models/trendz-settings.models';
 | 
			
		||||
import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils';
 | 
			
		||||
 | 
			
		||||
@Injectable({
 | 
			
		||||
  providedIn: 'root'
 | 
			
		||||
})
 | 
			
		||||
export class TrendzSettingsService {
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    private http: HttpClient
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  public getTrendzSettings(config?: RequestConfig): Observable<TrendzSettings> {
 | 
			
		||||
    return this.http.get<TrendzSettings>(`/api/trendz/settings`, defaultHttpOptionsFromConfig(config))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public saveTrendzSettings(trendzSettings: TrendzSettings, config?: RequestConfig): Observable<TrendzSettings> {
 | 
			
		||||
    return this.http.post<TrendzSettings>(`/api/trendz/settings`, trendzSettings, defaultHttpOptionsFromConfig(config))
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -104,7 +104,8 @@ export enum MenuId {
 | 
			
		||||
  features = 'features',
 | 
			
		||||
  otaUpdates = 'otaUpdates',
 | 
			
		||||
  version_control = 'version_control',
 | 
			
		||||
  api_usage = 'api_usage'
 | 
			
		||||
  api_usage = 'api_usage',
 | 
			
		||||
  trendz_settings = 'trendz_settings'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
declare type MenuFilter = (authState: AuthState) => boolean;
 | 
			
		||||
@ -684,6 +685,17 @@ export const menuSectionMap = new Map<MenuId, MenuSection>([
 | 
			
		||||
      path: '/usage',
 | 
			
		||||
      icon: 'insert_chart'
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  [
 | 
			
		||||
    MenuId.trendz_settings,
 | 
			
		||||
    {
 | 
			
		||||
      id: MenuId.trendz_settings,
 | 
			
		||||
      name: 'admin.trendz',
 | 
			
		||||
      fullName: 'admin.trendz-settings',
 | 
			
		||||
      type: 'link',
 | 
			
		||||
      path: '/settings/trendz',
 | 
			
		||||
      icon: 'trendz-settings'
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
@ -843,7 +855,8 @@ const defaultUserMenuMap = new Map<Authority, MenuReference[]>([
 | 
			
		||||
          {id: MenuId.home_settings},
 | 
			
		||||
          {id: MenuId.notification_settings},
 | 
			
		||||
          {id: MenuId.repository_settings},
 | 
			
		||||
          {id: MenuId.auto_commit_settings}
 | 
			
		||||
          {id: MenuId.auto_commit_settings},
 | 
			
		||||
          {id: MenuId.trendz_settings}
 | 
			
		||||
        ]
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
@ -946,7 +959,7 @@ const defaultHomeSectionMap = new Map<Authority, HomeSectionReference[]>([
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        name: 'admin.system-settings',
 | 
			
		||||
        places: [MenuId.home_settings, MenuId.resources_library, MenuId.repository_settings, MenuId.auto_commit_settings]
 | 
			
		||||
        places: [MenuId.home_settings, MenuId.resources_library, MenuId.repository_settings, MenuId.auto_commit_settings, MenuId.trendz_settings]
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  ],
 | 
			
		||||
 | 
			
		||||
@ -52,6 +52,7 @@ import { UiSettingsService } from '@core/http/ui-settings.service';
 | 
			
		||||
import { UsageInfoService } from '@core/http/usage-info.service';
 | 
			
		||||
import { EventService } from '@core/http/event.service';
 | 
			
		||||
import { AuditLogService } from '@core/http/audit-log.service';
 | 
			
		||||
import { TrendzSettingsService } from '@core/http/trendz-settings.service';
 | 
			
		||||
 | 
			
		||||
export const ServicesMap = new Map<string, Type<any>>(
 | 
			
		||||
  [
 | 
			
		||||
@ -91,6 +92,7 @@ export const ServicesMap = new Map<string, Type<any>>(
 | 
			
		||||
   ['usageInfoService', UsageInfoService],
 | 
			
		||||
   ['notificationService', NotificationService],
 | 
			
		||||
   ['eventService', EventService],
 | 
			
		||||
   ['auditLogService', AuditLogService]
 | 
			
		||||
   ['auditLogService', AuditLogService],
 | 
			
		||||
   ['trendzSettingsService', TrendzSettingsService]
 | 
			
		||||
  ]
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
@ -46,6 +46,7 @@ import { ScadaSymbolData } from '@home/pages/scada-symbol/scada-symbol-editor.mo
 | 
			
		||||
import { MenuId } from '@core/services/menu.models';
 | 
			
		||||
import { catchError } from 'rxjs/operators';
 | 
			
		||||
import { JsLibraryTableConfigResolver } from '@home/pages/admin/resource/js-library-table-config.resolver';
 | 
			
		||||
import { TrendzSettingsComponent } from '@home/pages/admin/trendz-settings.component';
 | 
			
		||||
 | 
			
		||||
export const scadaSymbolResolver: ResolveFn<ScadaSymbolData> =
 | 
			
		||||
  (route: ActivatedRouteSnapshot,
 | 
			
		||||
@ -349,6 +350,18 @@ const routes: Routes = [
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        path: 'trendz',
 | 
			
		||||
        component: TrendzSettingsComponent,
 | 
			
		||||
        canDeactivate: [ConfirmOnExitGuard],
 | 
			
		||||
        data: {
 | 
			
		||||
          auth: [Authority.TENANT_ADMIN],
 | 
			
		||||
          title: 'admin.trendz-settings',
 | 
			
		||||
          breadcrumb: {
 | 
			
		||||
            menuId: MenuId.trendz_settings
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        path: 'security-settings',
 | 
			
		||||
        redirectTo: '/security-settings/general'
 | 
			
		||||
 | 
			
		||||
@ -37,6 +37,7 @@ import { OAuth2Module } from '@home/pages/admin/oauth2/oauth2.module';
 | 
			
		||||
import { JsLibraryTableHeaderComponent } from '@home/pages/admin/resource/js-library-table-header.component';
 | 
			
		||||
import { JsResourceComponent } from '@home/pages/admin/resource/js-resource.component';
 | 
			
		||||
import { NgxFlowModule } from '@flowjs/ngx-flow';
 | 
			
		||||
import { TrendzSettingsComponent } from '@home/pages/admin/trendz-settings.component';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
  declarations:
 | 
			
		||||
@ -55,7 +56,8 @@ import { NgxFlowModule } from '@flowjs/ngx-flow';
 | 
			
		||||
      QueueComponent,
 | 
			
		||||
      RepositoryAdminSettingsComponent,
 | 
			
		||||
      AutoCommitAdminSettingsComponent,
 | 
			
		||||
      TwoFactorAuthSettingsComponent
 | 
			
		||||
      TwoFactorAuthSettingsComponent,
 | 
			
		||||
      TrendzSettingsComponent
 | 
			
		||||
    ],
 | 
			
		||||
  imports: [
 | 
			
		||||
    CommonModule,
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,51 @@
 | 
			
		||||
<!--
 | 
			
		||||
 | 
			
		||||
    Copyright © 2016-2025 The Thingsboard Authors
 | 
			
		||||
 | 
			
		||||
    Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
    you may not use this file except in compliance with the License.
 | 
			
		||||
    You may obtain a copy of the License at
 | 
			
		||||
 | 
			
		||||
        http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 | 
			
		||||
    Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
    distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
    See the License for the specific language governing permissions and
 | 
			
		||||
    limitations under the License.
 | 
			
		||||
 | 
			
		||||
-->
 | 
			
		||||
<div>
 | 
			
		||||
  <mat-card appearance="outlined" class="settings-card">
 | 
			
		||||
    <mat-card-header>
 | 
			
		||||
      <mat-card-title>
 | 
			
		||||
        <span class="mat-headline-5" translate>admin.trendz-settings</span>
 | 
			
		||||
      </mat-card-title>
 | 
			
		||||
      <span class="flex-1"></span>
 | 
			
		||||
      <div tb-help="trendzSettings"></div>
 | 
			
		||||
    </mat-card-header>
 | 
			
		||||
    <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
 | 
			
		||||
    </mat-progress-bar>
 | 
			
		||||
    <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
 | 
			
		||||
    <mat-card-content>
 | 
			
		||||
      <form [formGroup]="trendzSettingsForm" (ngSubmit)="save()">
 | 
			
		||||
        <fieldset [disabled]="isLoading$ | async">
 | 
			
		||||
          <section class="tb-trendz-section flex flex-col gt-sm:flex-row">
 | 
			
		||||
            <mat-form-field class="tb-trendz-url mat-block flex-1" subscriptSizing="dynamic">
 | 
			
		||||
              <mat-label translate>admin.trendz-url</mat-label>
 | 
			
		||||
              <input matInput formControlName="trendzUrl">
 | 
			
		||||
            </mat-form-field>
 | 
			
		||||
            <mat-checkbox class="flex flex-1" formControlName="isTrendzEnabled">
 | 
			
		||||
              {{ 'admin.trendz-enable' | translate }}
 | 
			
		||||
            </mat-checkbox>
 | 
			
		||||
          </section>
 | 
			
		||||
          <div class="flex w-full flex-row flex-wrap items-center justify-end">
 | 
			
		||||
            <button mat-button mat-raised-button color="primary" [disabled]="(isLoading$ | async) || trendzSettingsForm.invalid || !trendzSettingsForm.dirty" type="submit">
 | 
			
		||||
              {{'action.save' | translate}}
 | 
			
		||||
            </button>
 | 
			
		||||
          </div>
 | 
			
		||||
        </fieldset>
 | 
			
		||||
      </form>
 | 
			
		||||
    </mat-card-content>
 | 
			
		||||
  </mat-card>
 | 
			
		||||
</div>
 | 
			
		||||
@ -0,0 +1,36 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Copyright © 2016-2025 The Thingsboard Authors
 | 
			
		||||
 *
 | 
			
		||||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
 * you may not use this file except in compliance with the License.
 | 
			
		||||
 * You may obtain a copy of the License at
 | 
			
		||||
 *
 | 
			
		||||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
 *
 | 
			
		||||
 * Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
 * See the License for the specific language governing permissions and
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 @import "../../../../../scss/constants";
 | 
			
		||||
 | 
			
		||||
:host {
 | 
			
		||||
  .mat-mdc-card-header {
 | 
			
		||||
    min-height: 64px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .tb-trendz-section {
 | 
			
		||||
    margin: 16px 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .tb-trendz-url {
 | 
			
		||||
      @media #{$mat-gt-sm} {
 | 
			
		||||
        padding-right: 12px;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      @media #{$mat-lt-md} {
 | 
			
		||||
        padding-bottom: 12px;
 | 
			
		||||
      }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,94 @@
 | 
			
		||||
///
 | 
			
		||||
/// Copyright © 2016-2025 The Thingsboard Authors
 | 
			
		||||
///
 | 
			
		||||
/// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
/// you may not use this file except in compliance with the License.
 | 
			
		||||
/// You may obtain a copy of the License at
 | 
			
		||||
///
 | 
			
		||||
///     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
///
 | 
			
		||||
/// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
/// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
/// See the License for the specific language governing permissions and
 | 
			
		||||
/// limitations under the License.
 | 
			
		||||
///
 | 
			
		||||
 | 
			
		||||
import { Component, OnInit, DestroyRef } from '@angular/core';
 | 
			
		||||
import { PageComponent } from '@shared/components/page.component';
 | 
			
		||||
import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard';
 | 
			
		||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
 | 
			
		||||
import { TrendzSettingsService } from '@core/http/trendz-settings.service';
 | 
			
		||||
import { TrendzSettings } from '@shared/models/trendz-settings.models';
 | 
			
		||||
import { isDefinedAndNotNull } from '@core/utils';
 | 
			
		||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'tb-trendz-settings',
 | 
			
		||||
  templateUrl: './trendz-settings.component.html',
 | 
			
		||||
  styleUrls: ['./trendz-settings.component.scss', './settings-card.scss']
 | 
			
		||||
})
 | 
			
		||||
export class TrendzSettingsComponent extends PageComponent implements OnInit, HasConfirmForm {
 | 
			
		||||
 | 
			
		||||
  trendzSettingsForm: FormGroup;
 | 
			
		||||
 | 
			
		||||
  constructor(private fb: FormBuilder,
 | 
			
		||||
              private trendzSettingsService: TrendzSettingsService,
 | 
			
		||||
              private destroyRef: DestroyRef) {
 | 
			
		||||
    super();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnInit() {
 | 
			
		||||
    this.trendzSettingsForm = this.fb.group({
 | 
			
		||||
      trendzUrl: [null, [Validators.pattern(/^(https?:\/\/)[^\s/$.?#].[^\s]*$/i)]],
 | 
			
		||||
      isTrendzEnabled: [false]
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this.trendzSettingsService.getTrendzSettings().subscribe((trendzSettings) => {
 | 
			
		||||
      this.setTrendzSettings(trendzSettings);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this.trendzSettingsForm.get('isTrendzEnabled').valueChanges
 | 
			
		||||
          .pipe(takeUntilDestroyed(this.destroyRef))
 | 
			
		||||
          .subscribe((enabled: boolean) => this.toggleUrlRequired(enabled));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  toggleUrlRequired(enabled: boolean) {
 | 
			
		||||
    const trendzUrlControl = this.trendzSettingsForm.get('trendzUrl')!;
 | 
			
		||||
 | 
			
		||||
    if (enabled) {
 | 
			
		||||
      trendzUrlControl.addValidators(Validators.required);
 | 
			
		||||
    } else {
 | 
			
		||||
      trendzUrlControl.removeValidators(Validators.required);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    trendzUrlControl.updateValueAndValidity();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setTrendzSettings(trendzSettings: TrendzSettings) {
 | 
			
		||||
    this.trendzSettingsForm.reset({
 | 
			
		||||
      trendzUrl: trendzSettings?.baseUrl,
 | 
			
		||||
      isTrendzEnabled: trendzSettings?.enabled ?? false
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this.toggleUrlRequired(this.trendzSettingsForm.get('isTrendzEnabled').value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  confirmForm(): FormGroup {
 | 
			
		||||
    return this.trendzSettingsForm;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  save(): void {
 | 
			
		||||
    const trendzUrl = this.trendzSettingsForm.get('trendzUrl').value;
 | 
			
		||||
    const isTrendzEnabled =   this.trendzSettingsForm.get('isTrendzEnabled').value;
 | 
			
		||||
 | 
			
		||||
    const trendzSettings: TrendzSettings = {
 | 
			
		||||
      baseUrl: trendzUrl,
 | 
			
		||||
      enabled: isTrendzEnabled
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    this.trendzSettingsService.saveTrendzSettings(trendzSettings).subscribe(() => {
 | 
			
		||||
      this.setTrendzSettings(trendzSettings);
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -200,6 +200,7 @@ export const HelpLinks = {
 | 
			
		||||
    mobileQrCode: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/mobile-qr-code/`,
 | 
			
		||||
    calculatedField: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/calculated-fields/`,
 | 
			
		||||
    timewindowSettings: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/dashboards/#time-window`,
 | 
			
		||||
    trendzSettings: `${helpBaseUrl}/docs/trendz/`
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
/* eslint-enable max-len */
 | 
			
		||||
 | 
			
		||||
@ -62,7 +62,19 @@ export const svgIcons: {[key: string]: string} = {
 | 
			
		||||
    '4.6760606 4.678212,7.3604329 7.3397982,4.6839955 4.6657413,2.0041717 6.6653477,2.2309572e-4 9.3360035,2.6766286 11.997681,' +
 | 
			
		||||
    '0 14.659287,2.6765011 Z m -5.332255,4.0079963 1.999613,2.003945 -7.99844,8.0158157 -1.9996133,-2.004017 z m 1.676684,4.3522483 ' +
 | 
			
		||||
    '1.999613,2.0039454 -6.6654242,6.679793 -1.9996133,-2.003874 z m 2.988987,7.0033574 -1.999544,-2.003945 -4.6658108,4.675848 ' +
 | 
			
		||||
    '1.9996128,2.004015 z"/></svg>'
 | 
			
		||||
    '1.9996128,2.004015 z"/></svg>',
 | 
			
		||||
  'trendz-settings': '<svg viewBox="0 0 25 17"><path fill-rule="evenodd" clip-rule="evenodd" d="M7.04334 0.28949H12.2804V5.7615L11.5894 ' +
 | 
			
		||||
    '6.4537L10.4605 5.32674L7.04334 8.73801V0.28949ZM7.04334 10.0127V11.0075L7.54073 10.5093L7.04334 10.0127ZM7.04334 ' +
 | 
			
		||||
    '12.2649V13.2424L7.53209 12.7545L7.04334 12.2649ZM18.3903 13.243V12.2646L17.901 12.7546L18.3903 13.243ZM18.3903 ' +
 | 
			
		||||
    '11.0079V10.0123L17.8925 10.5093L18.3903 11.0079ZM18.3903 8.73841V3.34443H13.1532V5.76189L13.8438 6.45362L14.9727 ' +
 | 
			
		||||
    '5.32661L17.0542 7.40453L18.3903 8.73841ZM24.8335 1.16233H19.2631V13.8185H24.8335V1.16233ZM0.833481 5.52653H6.1705V13.8185H0.833481V5.52653Z"/>' +
 | 
			
		||||
    '<path fill-rule="evenodd" clip-rule="evenodd" d="M14.9729 6.55688L15.819 7.40149L14.6876 8.53099L15.8137 9.65905L16.947 ' +
 | 
			
		||||
    '8.52767L17.7931 9.37227L16.6583 10.5051L17.7844 11.6331L16.6669 12.7526L17.7931 13.8768L16.947 14.7214L15.8223 13.5986L14.6962 ' +
 | 
			
		||||
    '14.7267L15.819 15.8476L14.973 16.6921L13.8516 15.5727L12.7169 16.7094L11.5821 15.5727L10.4607 16.6921L9.61465 15.8476L10.7375 ' +
 | 
			
		||||
    '14.7267L9.61134 13.5985L8.48664 14.7213L7.64059 13.8767L8.76673 12.7525L7.64928 11.6331L8.77533 10.5051L7.64059 9.37227L8.48664 ' +
 | 
			
		||||
    '8.52767L9.61993 9.65905L10.7461 8.53103L9.61465 7.40158L10.4607 6.55697L11.5907 7.68499L12.7169 6.55688L13.843 7.68494L14.9729 ' +
 | 
			
		||||
    '6.55688ZM12.7169 8.24609L13.5629 9.0907L10.1787 12.4691L9.33268 11.6245L12.7169 8.24609ZM13.4262 10.0805L14.2723 10.9251L11.4521 ' +
 | 
			
		||||
    '13.7404L10.6061 12.8958L13.4262 10.0805ZM14.6909 13.0321L13.8449 12.1875L11.8708 14.1583L12.7168 15.0029L14.6909 13.0321Z"/></svg>'
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const svgIconsUrl: { [key: string]: string } = {
 | 
			
		||||
 | 
			
		||||
@ -62,3 +62,4 @@ export * from './window-message.model';
 | 
			
		||||
export * from './usage.models';
 | 
			
		||||
export * from './query/query.models';
 | 
			
		||||
export * from './regex.constants';
 | 
			
		||||
export * from './trendz-settings.models'
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										25
									
								
								ui-ngx/src/app/shared/models/trendz-settings.models.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								ui-ngx/src/app/shared/models/trendz-settings.models.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
			
		||||
///
 | 
			
		||||
/// Copyright © 2016-2025 The Thingsboard Authors
 | 
			
		||||
///
 | 
			
		||||
/// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
/// you may not use this file except in compliance with the License.
 | 
			
		||||
/// You may obtain a copy of the License at
 | 
			
		||||
///
 | 
			
		||||
///     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
///
 | 
			
		||||
/// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
/// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
/// See the License for the specific language governing permissions and
 | 
			
		||||
/// limitations under the License.
 | 
			
		||||
///
 | 
			
		||||
 | 
			
		||||
export interface TrendzSettings {
 | 
			
		||||
  baseUrl: string,
 | 
			
		||||
  enabled: boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const initialTrendzSettings: TrendzSettings = {
 | 
			
		||||
  baseUrl: null,
 | 
			
		||||
  enabled: false
 | 
			
		||||
}
 | 
			
		||||
@ -545,7 +545,11 @@
 | 
			
		||||
        "slack-settings": "Slack settings",
 | 
			
		||||
        "mobile-settings": "Mobile settings",
 | 
			
		||||
        "firebase-service-account-file": "Firebase service account credentials JSON file",
 | 
			
		||||
        "select-firebase-service-account-file": "Drag and drop your Firebase service account credentials file or "
 | 
			
		||||
        "select-firebase-service-account-file": "Drag and drop your Firebase service account credentials file or ",
 | 
			
		||||
        "trendz": "Trendz",
 | 
			
		||||
        "trendz-settings": "Trendz settings",
 | 
			
		||||
        "trendz-url": "Trendz URL",
 | 
			
		||||
        "trendz-enable": "Enable Trendz"
 | 
			
		||||
      },
 | 
			
		||||
    "alarm": {
 | 
			
		||||
        "alarm": "Alarm",
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user