Browse Source

Introduce SystemParams object holding necessary UI system parameters

pull/8463/head
Igor Kulikov 4 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 io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.info.BuildProperties; import org.springframework.boot.info.BuildProperties;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController; 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.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 springfox.documentation.annotations.ApiIgnore;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@ApiIgnore @ApiIgnore
@RestController @RestController
@TbCoreComponent @TbCoreComponent
@RequestMapping("/api") @RequestMapping("/api")
@Slf4j @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) @Autowired(required = false)
private BuildProperties buildProperties; private BuildProperties buildProperties;
@Autowired
private EntitiesVersionControlService versionControlService;
@PostConstruct @PostConstruct
public void init() { public void init() {
JsonNode info = buildInfoObject(); JsonNode info = buildInfoObject();
@ -56,6 +88,62 @@ public class SystemInfoController {
return buildInfoObject(); 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() { private JsonNode buildInfoObject() {
ObjectMapper objectMapper = new ObjectMapper(); ObjectMapper objectMapper = new ObjectMapper();
ObjectNode infoObject = objectMapper.createObjectNode(); 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; edgesSupportEnabled: boolean;
hasRepository: boolean; hasRepository: boolean;
tbelEnabled: boolean; tbelEnabled: boolean;
persistDeviceStateToTelemetry: boolean;
userSettings: UserSettings; userSettings: UserSettings;
} }
export interface SysParams extends SysParamsState {
maxDatapointsLimit: number;
}
export interface AuthPayload extends SysParamsState { export interface AuthPayload extends SysParamsState {
authUser: AuthUser; authUser: AuthUser;
userDetails: User; userDetails: User;

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

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

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

@ -66,6 +66,11 @@ export const selectTbelEnabled = createSelector(
(state: AuthState) => state.tbelEnabled (state: AuthState) => state.tbelEnabled
); );
export const selectPersistDeviceStateToTelemetry = createSelector(
selectAuthState,
(state: AuthState) => state.persistDeviceStateToTelemetry
);
export const selectUserSettings = createSelector( export const selectUserSettings = createSelector(
selectAuthState, selectAuthState,
(state: AuthState) => state.userSettings (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 { JwtHelperService } from '@auth0/angular-jwt';
import { HttpClient } from '@angular/common/http'; 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 { catchError, map, mergeMap, tap } from 'rxjs/operators';
import { LoginRequest, LoginResponse, PublicLoginRequest } from '@shared/models/login.models'; import { LoginRequest, LoginResponse, PublicLoginRequest } from '@shared/models/login.models';
@ -31,25 +31,17 @@ import { ActionAuthAuthenticated, ActionAuthLoadUser, ActionAuthUnauthenticated
import { getCurrentAuthState, getCurrentAuthUser } from './auth.selectors'; import { getCurrentAuthState, getCurrentAuthUser } from './auth.selectors';
import { Authority } from '@shared/models/authority.enum'; import { Authority } from '@shared/models/authority.enum';
import { ActionSettingsChangeLanguage } from '@app/core/settings/settings.actions'; 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 { TranslateService } from '@ngx-translate/core';
import { AuthUser } from '@shared/models/user.model'; import { AuthUser } from '@shared/models/user.model';
import { TimeService } from '@core/services/time.service'; import { TimeService } from '@core/services/time.service';
import { UtilsService } from '@core/services/utils.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 { MatDialog, MatDialogConfig } from '@angular/material/dialog';
import { AlertDialogComponent } from '@shared/components/dialog/alert-dialog.component'; import { AlertDialogComponent } from '@shared/components/dialog/alert-dialog.component';
import { OAuth2ClientInfo, PlatformType } from '@shared/models/oauth2.models'; import { OAuth2ClientInfo, PlatformType } from '@shared/models/oauth2.models';
import { isMobileApp } from '@core/utils'; import { isMobileApp } from '@core/utils';
import { TwoFactorAuthProviderType, TwoFaProviderInfo } from '@shared/models/two-factor-auth.models'; import { TwoFactorAuthProviderType, TwoFaProviderInfo } from '@shared/models/two-factor-auth.models';
import { UserPasswordPolicy } from '@shared/models/settings.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({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -65,10 +57,7 @@ export class AuthService {
private route: ActivatedRoute, private route: ActivatedRoute,
private zone: NgZone, private zone: NgZone,
private utils: UtilsService, private utils: UtilsService,
private dashboardService: DashboardService,
private adminService: AdminService,
private translate: TranslateService, private translate: TranslateService,
private userSettingsService: UserSettingsService,
private dialog: MatDialog private dialog: MatDialog
) { ) {
} }
@ -400,7 +389,7 @@ export class AuthService {
authPayload.forceFullscreen = true; authPayload.forceFullscreen = true;
} }
if (authPayload.authUser.isPublic) { if (authPayload.authUser.isPublic) {
this.loadSystemParams(authPayload).subscribe( this.loadSystemParams().subscribe(
(sysParams) => { (sysParams) => {
authPayload = {...authPayload, ...sysParams}; authPayload = {...authPayload, ...sysParams};
loadUserSubject.next(authPayload); loadUserSubject.next(authPayload);
@ -421,7 +410,7 @@ export class AuthService {
if (this.userForceFullscreen(authPayload)) { if (this.userForceFullscreen(authPayload)) {
authPayload.forceFullscreen = true; authPayload.forceFullscreen = true;
} }
this.loadSystemParams(authPayload).subscribe( this.loadSystemParams().subscribe(
(sysParams) => { (sysParams) => {
authPayload = {...authPayload, ...sysParams}; authPayload = {...authPayload, ...sysParams};
let userLang; let userLang;
@ -455,57 +444,14 @@ export class AuthService {
return loadUserSubject; return loadUserSubject;
} }
private loadIsUserTokenAccessEnabled(authUser: AuthUser): Observable<boolean> { private loadSystemParams(): Observable<SysParamsState> {
if (authUser.authority === Authority.SYS_ADMIN || return this.http.get<SysParams>('/api/system/params', defaultHttpOptions()).pipe(
authUser.authority === Authority.TENANT_ADMIN) { map((sysParams) => {
return this.http.get<boolean>('/api/user/tokenAccessEnabled', defaultHttpOptions()); this.timeService.setMaxDatapointsLimit(sysParams.maxDatapointsLimit);
} else { return sysParams;
return of(false); }),
} catchError(() => of({} as SysParamsState))
} );
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({}))));
} }
public refreshJwtToken(loadUserElseStoreJwtToken = true): Observable<LoginResponse> { 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 private http: HttpClient
) {} ) {}
public loadMaxDatapointsLimit(): Observable<number> { public setMaxDatapointsLimit(limit: number) {
return this.http.get<number>('/api/dashboard/maxDatapointsLimit', this.maxDatapointsLimit = limit;
defaultHttpOptions(true)).pipe( if (!this.maxDatapointsLimit || this.maxDatapointsLimit <= MIN_LIMIT) {
map((limit) => { this.maxDatapointsLimit = MIN_LIMIT + 1;
this.maxDatapointsLimit = limit; }
if (!this.maxDatapointsLimit || this.maxDatapointsLimit <= MIN_LIMIT) {
this.maxDatapointsLimit = MIN_LIMIT + 1;
}
return this.maxDatapointsLimit;
})
);
} }
public matchesExistingInterval(min: number, max: number, intervalMs: number): boolean { 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 { HomeLinksComponent } from './home-links.component';
import { Authority } from '@shared/models/authority.enum'; 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 { HomeDashboard } from '@shared/models/dashboard.models';
import { DashboardService } from '@core/http/dashboard.service'; 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 { AppState } from '@core/core.state';
import { map } from 'rxjs/operators'; 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 sysAdminHomePageDashboardJson from '!raw-loader!./sys_admin_home_page.raw';
import tenantAdminHomePageDashboardJson from '!raw-loader!./tenant_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 customerUserHomePageDashboardJson from '!raw-loader!./customer_user_home_page.raw';
import { EntityKeyType } from '@shared/models/query/query.models';
@Injectable() @Injectable()
export class HomeDashboardResolver implements Resolve<HomeDashboard> { export class HomeDashboardResolver implements Resolve<HomeDashboard> {
@ -39,22 +44,43 @@ export class HomeDashboardResolver implements Resolve<HomeDashboard> {
resolve(): Observable<HomeDashboard> { resolve(): Observable<HomeDashboard> {
return this.dashboardService.getHomeDashboard().pipe( return this.dashboardService.getHomeDashboard().pipe(
map((dashboard) => { mergeMap((dashboard) => {
if (!dashboard) { if (!dashboard) {
let dashboard$: Observable<HomeDashboard>;
const authority = getCurrentAuthUser(this.store).authority; const authority = getCurrentAuthUser(this.store).authority;
switch (authority) { switch (authority) {
case Authority.SYS_ADMIN: case Authority.SYS_ADMIN:
dashboard = JSON.parse(sysAdminHomePageDashboardJson); dashboard$ = of(JSON.parse(sysAdminHomePageDashboardJson));
break; break;
case Authority.TENANT_ADMIN: case Authority.TENANT_ADMIN:
dashboard = JSON.parse(tenantAdminHomePageDashboardJson); dashboard$ = this.updateDeviceActivityKeyFilterIfNeeded(JSON.parse(tenantAdminHomePageDashboardJson));
break; break;
case Authority.CUSTOMER_USER: case Authority.CUSTOMER_USER:
dashboard = JSON.parse(customerUserHomePageDashboardJson); dashboard$ = this.updateDeviceActivityKeyFilterIfNeeded(JSON.parse(customerUserHomePageDashboardJson));
break; break;
} }
if (dashboard) { if (dashboard$) {
dashboard.hideDashboardToolbar = true; 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; return dashboard;

Loading…
Cancel
Save