Browse Source

Introduce SystemParams object holding necessary UI system parameters

pull/8463/head
Igor Kulikov 3 years ago
parent
commit
1f334d58ad
  1. 90
      application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java
  2. 33
      common/data/src/main/java/org/thingsboard/server/common/data/SystemParams.java
  3. 5
      ui-ngx/src/app/core/auth/auth.models.ts
  4. 1
      ui-ngx/src/app/core/auth/auth.reducer.ts
  5. 5
      ui-ngx/src/app/core/auth/auth.selectors.ts
  6. 98
      ui-ngx/src/app/core/auth/auth.service.ts
  7. 16
      ui-ngx/src/app/core/services/time.service.ts
  8. 44
      ui-ngx/src/app/modules/home/pages/home-links/home-links-routing.module.ts

90
application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java

@ -22,27 +22,59 @@ import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.info.BuildProperties;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.SystemParams;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.settings.UserSettings;
import org.thingsboard.server.common.data.settings.UserSettingsType;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal;
import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService;
import springfox.documentation.annotations.ApiIgnore;
import javax.annotation.PostConstruct;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@ApiIgnore
@RestController
@TbCoreComponent
@RequestMapping("/api")
@Slf4j
public class SystemInfoController {
public class SystemInfoController extends BaseController {
@Value("${security.user_token_access_enabled}")
private boolean userTokenAccessEnabled;
@Value("${tbel.enabled:true}")
private boolean tbelEnabled;
@Value("${state.persistToTelemetry:false}")
private boolean persistToTelemetry;
@Value("${ui.dashboard.max_datapoints_limit}")
private long maxDatapointsLimit;
@Autowired(required = false)
private BuildProperties buildProperties;
@Autowired
private EntitiesVersionControlService versionControlService;
@PostConstruct
public void init() {
JsonNode info = buildInfoObject();
@ -56,6 +88,62 @@ public class SystemInfoController {
return buildInfoObject();
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/system/params", method = RequestMethod.GET)
@ResponseBody
public SystemParams getSystemParams() throws ThingsboardException {
SystemParams systemParams = new SystemParams();
SecurityUser currentUser = getCurrentUser();
TenantId tenantId = currentUser.getTenantId();
CustomerId customerId = currentUser.getCustomerId();
if (currentUser.isSystemAdmin() || currentUser.isTenantAdmin()) {
systemParams.setUserTokenAccessEnabled(userTokenAccessEnabled);
} else {
systemParams.setUserTokenAccessEnabled(false);
}
boolean forceFullscreen = isForceFullscreen(currentUser);
if (forceFullscreen && (currentUser.isTenantAdmin() || currentUser.isCustomerUser())) {
PageLink pageLink = new PageLink(100);
List<DashboardInfo> dashboards;
if (currentUser.isTenantAdmin()) {
dashboards = dashboardService.findDashboardsByTenantId(tenantId, pageLink).getData();
} else {
dashboards = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).getData();
}
systemParams.setAllowedDashboardIds(dashboards.stream().map(d -> d.getId().getId().toString()).collect(Collectors.toList()));
} else {
systemParams.setAllowedDashboardIds(Collections.emptyList());
}
systemParams.setEdgesSupportEnabled(edgesEnabled);
if (currentUser.isTenantAdmin()) {
systemParams.setHasRepository(versionControlService.getVersionControlSettings(tenantId) != null);
systemParams.setTbelEnabled(tbelEnabled);
} else {
systemParams.setHasRepository(false);
systemParams.setTbelEnabled(false);
}
if (currentUser.isTenantAdmin() || currentUser.isCustomerUser()) {
systemParams.setPersistDeviceStateToTelemetry(persistToTelemetry);
} else {
systemParams.setPersistDeviceStateToTelemetry(false);
}
UserSettings userSettings = userSettingsService.findUserSettings(currentUser.getTenantId(), currentUser.getId(), UserSettingsType.GENERAL);
ObjectNode userSettingsNode = userSettings == null ? JacksonUtil.newObjectNode() : (ObjectNode) userSettings.getSettings();
if (!userSettingsNode.has("openedMenuSections")) {
userSettingsNode.set("openedMenuSections", JacksonUtil.newArrayNode());
}
systemParams.setUserSettings(userSettingsNode);
systemParams.setMaxDatapointsLimit(maxDatapointsLimit);
return systemParams;
}
private boolean isForceFullscreen(SecurityUser currentUser) {
return UserPrincipal.Type.PUBLIC_ID.equals(currentUser.getUserPrincipal().getType()) ||
(currentUser.getAdditionalInfo() != null &&
currentUser.getAdditionalInfo().has("defaultDashboardFullscreen") &&
currentUser.getAdditionalInfo().get("defaultDashboardFullscreen").booleanValue());
}
private JsonNode buildInfoObject() {
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode infoObject = objectMapper.createObjectNode();

33
common/data/src/main/java/org/thingsboard/server/common/data/SystemParams.java

@ -0,0 +1,33 @@
/**
* Copyright © 2016-2023 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;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
import java.util.List;
@Data
public class SystemParams {
boolean userTokenAccessEnabled;
List<String> allowedDashboardIds;
boolean edgesSupportEnabled;
boolean hasRepository;
boolean tbelEnabled;
boolean persistDeviceStateToTelemetry;
JsonNode userSettings;
long maxDatapointsLimit;
}

5
ui-ngx/src/app/core/auth/auth.models.ts

@ -23,9 +23,14 @@ export interface SysParamsState {
edgesSupportEnabled: boolean;
hasRepository: boolean;
tbelEnabled: boolean;
persistDeviceStateToTelemetry: boolean;
userSettings: UserSettings;
}
export interface SysParams extends SysParamsState {
maxDatapointsLimit: number;
}
export interface AuthPayload extends SysParamsState {
authUser: AuthUser;
userDetails: User;

1
ui-ngx/src/app/core/auth/auth.reducer.ts

@ -27,6 +27,7 @@ const emptyUserAuthState: AuthPayload = {
edgesSupportEnabled: false,
hasRepository: false,
tbelEnabled: false,
persistDeviceStateToTelemetry: false,
userSettings: initialUserSettings
};

5
ui-ngx/src/app/core/auth/auth.selectors.ts

@ -66,6 +66,11 @@ export const selectTbelEnabled = createSelector(
(state: AuthState) => state.tbelEnabled
);
export const selectPersistDeviceStateToTelemetry = createSelector(
selectAuthState,
(state: AuthState) => state.persistDeviceStateToTelemetry
);
export const selectUserSettings = createSelector(
selectAuthState,
(state: AuthState) => state.userSettings

98
ui-ngx/src/app/core/auth/auth.service.ts

@ -18,7 +18,7 @@ import { Injectable, NgZone } from '@angular/core';
import { JwtHelperService } from '@auth0/angular-jwt';
import { HttpClient } from '@angular/common/http';
import { forkJoin, Observable, of, ReplaySubject, throwError } from 'rxjs';
import { Observable, of, ReplaySubject, throwError } from 'rxjs';
import { catchError, map, mergeMap, tap } from 'rxjs/operators';
import { LoginRequest, LoginResponse, PublicLoginRequest } from '@shared/models/login.models';
@ -31,25 +31,17 @@ import { ActionAuthAuthenticated, ActionAuthLoadUser, ActionAuthUnauthenticated
import { getCurrentAuthState, getCurrentAuthUser } from './auth.selectors';
import { Authority } from '@shared/models/authority.enum';
import { ActionSettingsChangeLanguage } from '@app/core/settings/settings.actions';
import { AuthPayload, AuthState, SysParamsState } from '@core/auth/auth.models';
import { AuthPayload, AuthState, SysParams, SysParamsState } from '@core/auth/auth.models';
import { TranslateService } from '@ngx-translate/core';
import { AuthUser } from '@shared/models/user.model';
import { TimeService } from '@core/services/time.service';
import { UtilsService } from '@core/services/utils.service';
import { DashboardService } from '@core/http/dashboard.service';
import { PageLink } from '@shared/models/page/page-link';
import { DashboardInfo } from '@shared/models/dashboard.models';
import { PageData } from '@app/shared/models/page/page-data';
import { AdminService } from '@core/http/admin.service';
import { ActionNotificationShow } from '@core/notification/notification.actions';
import { MatDialog, MatDialogConfig } from '@angular/material/dialog';
import { AlertDialogComponent } from '@shared/components/dialog/alert-dialog.component';
import { OAuth2ClientInfo, PlatformType } from '@shared/models/oauth2.models';
import { isMobileApp } from '@core/utils';
import { TwoFactorAuthProviderType, TwoFaProviderInfo } from '@shared/models/two-factor-auth.models';
import { UserPasswordPolicy } from '@shared/models/settings.models';
import { UserSettings } from '@shared/models/user-settings.models';
import { UserSettingsService } from '@core/http/user-settings.service';
@Injectable({
providedIn: 'root'
@ -65,10 +57,7 @@ export class AuthService {
private route: ActivatedRoute,
private zone: NgZone,
private utils: UtilsService,
private dashboardService: DashboardService,
private adminService: AdminService,
private translate: TranslateService,
private userSettingsService: UserSettingsService,
private dialog: MatDialog
) {
}
@ -400,7 +389,7 @@ export class AuthService {
authPayload.forceFullscreen = true;
}
if (authPayload.authUser.isPublic) {
this.loadSystemParams(authPayload).subscribe(
this.loadSystemParams().subscribe(
(sysParams) => {
authPayload = {...authPayload, ...sysParams};
loadUserSubject.next(authPayload);
@ -421,7 +410,7 @@ export class AuthService {
if (this.userForceFullscreen(authPayload)) {
authPayload.forceFullscreen = true;
}
this.loadSystemParams(authPayload).subscribe(
this.loadSystemParams().subscribe(
(sysParams) => {
authPayload = {...authPayload, ...sysParams};
let userLang;
@ -455,57 +444,14 @@ export class AuthService {
return loadUserSubject;
}
private loadIsUserTokenAccessEnabled(authUser: AuthUser): Observable<boolean> {
if (authUser.authority === Authority.SYS_ADMIN ||
authUser.authority === Authority.TENANT_ADMIN) {
return this.http.get<boolean>('/api/user/tokenAccessEnabled', defaultHttpOptions());
} else {
return of(false);
}
}
public loadIsEdgesSupportEnabled(): Observable<boolean> {
return this.http.get<boolean>('/api/edges/enabled', defaultHttpOptions());
}
private loadHasRepository(authUser: AuthUser): Observable<boolean> {
if (authUser.authority === Authority.TENANT_ADMIN) {
return this.http.get<boolean>('/api/admin/repositorySettings/exists', defaultHttpOptions());
} else {
return of(false);
}
}
private loadTbelEnabled(authUser: AuthUser): Observable<boolean> {
if (authUser.authority === Authority.TENANT_ADMIN) {
return this.http.get<boolean>('/api/ruleChain/tbelEnabled', defaultHttpOptions());
} else {
return of(false);
}
}
private loadUserSettings(): Observable<UserSettings> {
return this.userSettingsService.loadUserSettings();
}
private loadSystemParams(authPayload: AuthPayload): Observable<SysParamsState> {
const sources = [this.loadIsUserTokenAccessEnabled(authPayload.authUser),
this.fetchAllowedDashboardIds(authPayload),
this.loadIsEdgesSupportEnabled(),
this.loadHasRepository(authPayload.authUser),
this.loadTbelEnabled(authPayload.authUser),
this.loadUserSettings(),
this.timeService.loadMaxDatapointsLimit()];
return forkJoin(sources)
.pipe(map((data) => {
const userTokenAccessEnabled: boolean = data[0] as boolean;
const allowedDashboardIds: string[] = data[1] as string[];
const edgesSupportEnabled: boolean = data[2] as boolean;
const hasRepository: boolean = data[3] as boolean;
const tbelEnabled: boolean = data[4] as boolean;
const userSettings = data[5] as UserSettings;
return {userTokenAccessEnabled, allowedDashboardIds, edgesSupportEnabled, hasRepository, tbelEnabled, userSettings};
}, catchError((err) => of({}))));
private loadSystemParams(): Observable<SysParamsState> {
return this.http.get<SysParams>('/api/system/params', defaultHttpOptions()).pipe(
map((sysParams) => {
this.timeService.setMaxDatapointsLimit(sysParams.maxDatapointsLimit);
return sysParams;
}),
catchError(() => of({} as SysParamsState))
);
}
public refreshJwtToken(loadUserElseStoreJwtToken = true): Observable<LoginResponse> {
@ -689,24 +635,4 @@ export class AuthService {
}
}
private fetchAllowedDashboardIds(authPayload: AuthPayload): Observable<string[]> {
if (authPayload.forceFullscreen && (authPayload.authUser.authority === Authority.TENANT_ADMIN ||
authPayload.authUser.authority === Authority.CUSTOMER_USER)) {
const pageLink = new PageLink(100);
let fetchDashboardsObservable: Observable<PageData<DashboardInfo>>;
if (authPayload.authUser.authority === Authority.TENANT_ADMIN) {
fetchDashboardsObservable = this.dashboardService.getTenantDashboards(pageLink);
} else {
fetchDashboardsObservable = this.dashboardService.getCustomerDashboards(authPayload.authUser.customerId, pageLink);
}
return fetchDashboardsObservable.pipe(
map((result) => {
const dashboards = result.data;
return dashboards.map(dashboard => dashboard.id.id);
})
);
} else {
return of([]);
}
}
}

16
ui-ngx/src/app/core/services/time.service.ts

@ -53,17 +53,11 @@ export class TimeService {
private http: HttpClient
) {}
public loadMaxDatapointsLimit(): Observable<number> {
return this.http.get<number>('/api/dashboard/maxDatapointsLimit',
defaultHttpOptions(true)).pipe(
map((limit) => {
this.maxDatapointsLimit = limit;
if (!this.maxDatapointsLimit || this.maxDatapointsLimit <= MIN_LIMIT) {
this.maxDatapointsLimit = MIN_LIMIT + 1;
}
return this.maxDatapointsLimit;
})
);
public setMaxDatapointsLimit(limit: number) {
this.maxDatapointsLimit = limit;
if (!this.maxDatapointsLimit || this.maxDatapointsLimit <= MIN_LIMIT) {
this.maxDatapointsLimit = MIN_LIMIT + 1;
}
}
public matchesExistingInterval(min: number, max: number, intervalMs: number): boolean {

44
ui-ngx/src/app/modules/home/pages/home-links/home-links-routing.module.ts

@ -19,16 +19,21 @@ import { Resolve, RouterModule, Routes } from '@angular/router';
import { HomeLinksComponent } from './home-links.component';
import { Authority } from '@shared/models/authority.enum';
import { Observable } from 'rxjs';
import { mergeMap, Observable, of } from 'rxjs';
import { HomeDashboard } from '@shared/models/dashboard.models';
import { DashboardService } from '@core/http/dashboard.service';
import { Store } from '@ngrx/store';
import { select, Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { map } from 'rxjs/operators';
import { getCurrentAuthUser } from '@core/auth/auth.selectors';
import {
getCurrentAuthUser,
selectHasRepository,
selectPersistDeviceStateToTelemetry
} from '@core/auth/auth.selectors';
import sysAdminHomePageDashboardJson from '!raw-loader!./sys_admin_home_page.raw';
import tenantAdminHomePageDashboardJson from '!raw-loader!./tenant_admin_home_page.raw';
import customerUserHomePageDashboardJson from '!raw-loader!./customer_user_home_page.raw';
import { EntityKeyType } from '@shared/models/query/query.models';
@Injectable()
export class HomeDashboardResolver implements Resolve<HomeDashboard> {
@ -39,22 +44,43 @@ export class HomeDashboardResolver implements Resolve<HomeDashboard> {
resolve(): Observable<HomeDashboard> {
return this.dashboardService.getHomeDashboard().pipe(
map((dashboard) => {
mergeMap((dashboard) => {
if (!dashboard) {
let dashboard$: Observable<HomeDashboard>;
const authority = getCurrentAuthUser(this.store).authority;
switch (authority) {
case Authority.SYS_ADMIN:
dashboard = JSON.parse(sysAdminHomePageDashboardJson);
dashboard$ = of(JSON.parse(sysAdminHomePageDashboardJson));
break;
case Authority.TENANT_ADMIN:
dashboard = JSON.parse(tenantAdminHomePageDashboardJson);
dashboard$ = this.updateDeviceActivityKeyFilterIfNeeded(JSON.parse(tenantAdminHomePageDashboardJson));
break;
case Authority.CUSTOMER_USER:
dashboard = JSON.parse(customerUserHomePageDashboardJson);
dashboard$ = this.updateDeviceActivityKeyFilterIfNeeded(JSON.parse(customerUserHomePageDashboardJson));
break;
}
if (dashboard) {
dashboard.hideDashboardToolbar = true;
if (dashboard$) {
return dashboard$.pipe(
map((homeDashboard) => {
homeDashboard.hideDashboardToolbar = true;
return homeDashboard;
})
);
}
}
return of(dashboard);
})
);
}
private updateDeviceActivityKeyFilterIfNeeded(dashboard: HomeDashboard): Observable<HomeDashboard> {
return this.store.pipe(select(selectPersistDeviceStateToTelemetry)).pipe(
map((persistToTelemetry) => {
if (persistToTelemetry) {
for (const filterId of Object.keys(dashboard.configuration.filters)) {
if (['Active Devices', 'Inactive Devices'].includes(dashboard.configuration.filters[filterId].filter)) {
dashboard.configuration.filters[filterId].keyFilters[0].key.type = EntityKeyType.TIME_SERIES;
}
}
}
return dashboard;

Loading…
Cancel
Save