Browse Source

UI: Metrial icons selector

pull/8946/head
Igor Kulikov 3 years ago
parent
commit
7861a3fbad
  1. 30
      application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java
  2. 22
      ui-ngx/src/app/core/services/resources.service.ts
  3. 15
      ui-ngx/src/app/shared/components/dialog/material-icons-dialog.component.ts
  4. 24
      ui-ngx/src/app/shared/components/material-icons.component.ts
  5. 36
      ui-ngx/src/app/shared/models/icon.models.ts
  6. BIN
      ui-ngx/src/assets/fonts/MaterialIcons-Regular.ttf
  7. 6367
      ui-ngx/src/assets/metadata/material-icons.json

30
application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java

@ -19,9 +19,11 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.config.annotation.ObjectPostProcessor;
@ -29,7 +31,6 @@ import org.springframework.security.config.annotation.authentication.builders.Au
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
@ -37,9 +38,11 @@ import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.header.writers.StaticHeadersWriter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.filter.ShallowEtagHeaderFilter;
import org.thingsboard.server.dao.oauth2.OAuth2Configuration;
import org.thingsboard.server.exception.ThingsboardErrorResponseHandler;
import org.thingsboard.server.queue.util.TbCoreComponent;
@ -119,6 +122,17 @@ public class ThingsboardSecurityConfiguration {
@Autowired private RateLimitProcessingFilter rateLimitProcessingFilter;
@Bean
protected FilterRegistrationBean<ShallowEtagHeaderFilter> buildEtagFilter() throws Exception {
ShallowEtagHeaderFilter etagFilter = new ShallowEtagHeaderFilter();
etagFilter.setWriteWeakETag(true);
FilterRegistrationBean<ShallowEtagHeaderFilter> filterRegistrationBean
= new FilterRegistrationBean<>( etagFilter);
filterRegistrationBean.addUrlPatterns("*.js","*.css","*.ico","/assets/*","/static/*");
filterRegistrationBean.setName("etagFilter");
return filterRegistrationBean;
}
@Bean
protected RestLoginProcessingFilter buildRestLoginProcessingFilter() throws Exception {
RestLoginProcessingFilter filter = new RestLoginProcessingFilter(FORM_BASED_LOGIN_ENTRY_POINT, successHandler, failureHandler);
@ -181,8 +195,18 @@ public class ThingsboardSecurityConfiguration {
private OAuth2AuthorizationRequestResolver oAuth2AuthorizationRequestResolver;
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().antMatchers("/*.js","/*.css","/*.ico","/assets/**","/static/**");
@Order(0)
SecurityFilterChain resources(HttpSecurity http) throws Exception {
http
.requestMatchers((matchers) -> matchers.antMatchers("/*.js","/*.css","/*.ico","/assets/**","/static/**"))
.headers().defaultsDisabled()
.addHeaderWriter(new StaticHeadersWriter(HttpHeaders.CACHE_CONTROL, "max-age=0, public"))
.and()
.authorizeHttpRequests((authorize) -> authorize.anyRequest().permitAll())
.requestCache().disable()
.securityContext().disable()
.sessionManagement().disable();
return http.build();
}
@Bean

22
ui-ngx/src/app/core/services/resources.service.ts

@ -47,6 +47,7 @@ export interface ModulesWithFactories {
})
export class ResourcesService {
private loadedJsonResources: { [url: string]: ReplaySubject<any> } = {};
private loadedResources: { [url: string]: ReplaySubject<void> } = {};
private loadedModules: { [url: string]: ReplaySubject<Type<any>[]> } = {};
private loadedModulesAndFactories: { [url: string]: ReplaySubject<ModulesWithFactories> } = {};
@ -61,6 +62,27 @@ export class ResourcesService {
this.store.pipe(select(selectIsAuthenticated)).subscribe(() => this.clearModulesCache());
}
public loadJsonResource<T>(url: string): Observable<T> {
if (this.loadedJsonResources[url]) {
return this.loadedJsonResources[url].asObservable();
}
const subject = new ReplaySubject<any>();
this.loadedJsonResources[url] = subject;
this.http.get(url).subscribe(
{
next: (o) => {
this.loadedJsonResources[url].next(o);
this.loadedJsonResources[url].complete();
},
error: () => {
this.loadedJsonResources[url].error(new Error(`Unable to load ${url}`));
delete this.loadedJsonResources[url];
}
}
);
return subject.asObservable();
}
public loadResource(url: string): Observable<any> {
if (this.loadedResources[url]) {
return this.loadedResources[url].asObservable();

15
ui-ngx/src/app/shared/components/dialog/material-icons-dialog.component.ts

@ -22,8 +22,10 @@ import { Router } from '@angular/router';
import { DialogComponent } from '@shared/components/dialog.component';
import { UtilsService } from '@core/services/utils.service';
import { UntypedFormControl } from '@angular/forms';
import { merge, Observable, of } from 'rxjs';
import { merge, Observable } from 'rxjs';
import { delay, map, mapTo, mergeMap, share, startWith, tap } from 'rxjs/operators';
import { ResourcesService } from '@core/services/resources.service';
import { getMaterialIcons } from '@shared/models/icon.models';
export interface MaterialIconsDialogData {
icon: string;
@ -50,6 +52,7 @@ export class MaterialIconsDialogComponent extends DialogComponent<MaterialIconsD
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: MaterialIconsDialogData,
private utils: UtilsService,
private resourcesService: ResourcesService,
public dialogRef: MatDialogRef<MaterialIconsDialogComponent, string>) {
super(store, router, dialogRef);
this.selectedIcon = data.icon;
@ -58,15 +61,13 @@ export class MaterialIconsDialogComponent extends DialogComponent<MaterialIconsD
ngOnInit(): void {
this.icons$ = this.showAllControl.valueChanges.pipe(
map((showAll) => {
return {firstTime: false, showAll};
}),
startWith<{firstTime: boolean, showAll: boolean}>({firstTime: true, showAll: false}),
map((showAll) => ({firstTime: false, showAll})),
startWith<{firstTime: boolean; showAll: boolean}>({firstTime: true, showAll: false}),
mergeMap((data) => {
const res = getMaterialIcons(this.resourcesService, data.showAll, '');
if (data.showAll) {
return this.utils.getMaterialIcons().pipe(delay(100));
return res.pipe(delay(100));
} else {
const res = of(this.utils.getCommonMaterialIcons());
return data.firstTime ? res : res.pipe(delay(50));
}
}),

24
ui-ngx/src/app/shared/components/material-icons.component.ts

@ -0,0 +1,24 @@
import { PageComponent } from '@shared/components/page.component';
import { OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { UntypedFormControl } from '@angular/forms';
import { BehaviorSubject, Observable, ReplaySubject } from 'rxjs';
export class MaterialIconsComponent extends PageComponent implements OnInit {
searchIconsControl: UntypedFormControl;
showAllSubject = new BehaviorSubject<boolean>(false);
icons$: Observable<Array<string>>;
constructor(protected store: Store<AppState>) {
super(store);
this.searchIconsControl = new UntypedFormControl('');
}
ngOnInit(): void {
}
}

36
ui-ngx/src/app/shared/models/icon.models.ts

@ -0,0 +1,36 @@
import { Unit, units } from '@shared/models/unit.models';
import { ResourcesService } from '@core/services/resources.service';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { isEmptyStr, isNotEmptyStr } from '@core/utils';
export interface MaterialIcon {
name: string;
tags: string[];
}
export const iconByName = (icons: Array<MaterialIcon>, name: string): MaterialIcon => icons.find(i => i.name === name);
const searchIconTags = (icon: MaterialIcon, searchText: string): boolean =>
!!icon.tags.find(t => t.toUpperCase().includes(searchText.toUpperCase()));
const searchIcons = (_icons: Array<MaterialIcon>, searchText: string): Array<MaterialIcon> => _icons.filter(
i => i.name.toUpperCase().includes(searchText.toUpperCase()) ||
searchIconTags(i, searchText)
);
const getCommonMaterialIcons = (icons: Array<MaterialIcon>): Array<MaterialIcon> => icons.slice(0, 44);
export const getMaterialIcons = (resourcesService: ResourcesService, all = false, searchText: string): Observable<string[]> =>
resourcesService.loadJsonResource<Array<MaterialIcon>>('/assets/metadata/material-icons.json').pipe(
map((icons) => {
if (isNotEmptyStr(searchText)) {
return searchIcons(icons, searchText);
} else if (!all) {
return getCommonMaterialIcons(icons);
} else {
return icons;
}
}),
map((icons) => icons.map(icon => icon.name))
);

BIN
ui-ngx/src/assets/fonts/MaterialIcons-Regular.ttf

Binary file not shown.

6367
ui-ngx/src/assets/metadata/material-icons.json

File diff suppressed because it is too large
Loading…
Cancel
Save