Commit bfc03c4c69c23360607fcd8e8e9cc6f646f6dbc6
Committed by
GitHub
Merge pull request #4293 from vvlladd28/feature/resources/add-new-type
[3.3] UI: Added manager resources; added new entity type TB_RESOURCE
Showing
14 changed files
with
635 additions
and
21 deletions
ui-ngx/src/app/core/http/resource.service.ts
0 → 100644
1 | +/// | |
2 | +/// Copyright © 2016-2021 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { Injectable } from '@angular/core'; | |
18 | +import { HttpClient } from '@angular/common/http'; | |
19 | +import { PageLink } from '@shared/models/page/page-link'; | |
20 | +import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils'; | |
21 | +import { Observable } from 'rxjs'; | |
22 | +import { PageData } from '@shared/models/page/page-data'; | |
23 | +import { Resource, ResourceInfo } from '@shared/models/resource.models'; | |
24 | +import { map } from 'rxjs/operators'; | |
25 | + | |
26 | +@Injectable({ | |
27 | + providedIn: 'root' | |
28 | +}) | |
29 | +export class ResourceService { | |
30 | + constructor( | |
31 | + private http: HttpClient | |
32 | + ) { | |
33 | + | |
34 | + } | |
35 | + | |
36 | + public getResources(pageLink: PageLink, config?: RequestConfig): Observable<PageData<ResourceInfo>> { | |
37 | + return this.http.get<PageData<ResourceInfo>>(`/api/resource${pageLink.toQuery()}`, | |
38 | + defaultHttpOptionsFromConfig(config)); | |
39 | + } | |
40 | + | |
41 | + public getResource(resourceId: string, config?: RequestConfig): Observable<Resource> { | |
42 | + return this.http.get<Resource>(`/api/resource/${resourceId}`, defaultHttpOptionsFromConfig(config)); | |
43 | + } | |
44 | + | |
45 | + public downloadResource(resourceId: string): Observable<any> { | |
46 | + return this.http.get(`/api/resource/${resourceId}/download`, { responseType: 'arraybuffer', observe: 'response' }).pipe( | |
47 | + map((response) => { | |
48 | + const headers = response.headers; | |
49 | + const filename = headers.get('x-filename'); | |
50 | + const contentType = headers.get('content-type'); | |
51 | + const linkElement = document.createElement('a'); | |
52 | + try { | |
53 | + const blob = new Blob([response.body], { type: contentType }); | |
54 | + const url = URL.createObjectURL(blob); | |
55 | + linkElement.setAttribute('href', url); | |
56 | + linkElement.setAttribute('download', filename); | |
57 | + const clickEvent = new MouseEvent('click', | |
58 | + { | |
59 | + view: window, | |
60 | + bubbles: true, | |
61 | + cancelable: false | |
62 | + } | |
63 | + ); | |
64 | + linkElement.dispatchEvent(clickEvent); | |
65 | + return null; | |
66 | + } catch (e) { | |
67 | + throw e; | |
68 | + } | |
69 | + }) | |
70 | + ); | |
71 | + } | |
72 | + | |
73 | + public saveResource(resource: Resource, config?: RequestConfig): Observable<Resource> { | |
74 | + return this.http.post<Resource>('/api/resource', resource, defaultHttpOptionsFromConfig(config)); | |
75 | + } | |
76 | + | |
77 | + public deleteResource(resourceId: string, config?: RequestConfig) { | |
78 | + return this.http.delete(`/api/resource/${resourceId}`, defaultHttpOptionsFromConfig(config)); | |
79 | + } | |
80 | + | |
81 | +} | ... | ... |
... | ... | @@ -105,6 +105,13 @@ export class MenuService { |
105 | 105 | }, |
106 | 106 | { |
107 | 107 | id: guid(), |
108 | + name: 'resource.resources-library', | |
109 | + type: 'link', | |
110 | + path: '/resources-library', | |
111 | + icon: 'folder' | |
112 | + }, | |
113 | + { | |
114 | + id: guid(), | |
108 | 115 | name: 'admin.system-settings', |
109 | 116 | type: 'toggle', |
110 | 117 | path: '/settings', |
... | ... | @@ -182,6 +189,16 @@ export class MenuService { |
182 | 189 | ] |
183 | 190 | }, |
184 | 191 | { |
192 | + name: 'resource.management', | |
193 | + places: [ | |
194 | + { | |
195 | + name: 'resource.resources-library', | |
196 | + icon: 'folder', | |
197 | + path: '/resources-library' | |
198 | + } | |
199 | + ] | |
200 | + }, | |
201 | + { | |
185 | 202 | name: 'admin.system-settings', |
186 | 203 | places: [ |
187 | 204 | { |
... | ... | @@ -285,6 +302,13 @@ export class MenuService { |
285 | 302 | }, |
286 | 303 | { |
287 | 304 | id: guid(), |
305 | + name: 'resource.resources-library', | |
306 | + type: 'link', | |
307 | + path: '/resources-library', | |
308 | + icon: 'folder' | |
309 | + }, | |
310 | + { | |
311 | + id: guid(), | |
288 | 312 | name: 'admin.home-settings', |
289 | 313 | type: 'link', |
290 | 314 | path: '/settings/home', |
... | ... | @@ -369,6 +393,16 @@ export class MenuService { |
369 | 393 | ] |
370 | 394 | }, |
371 | 395 | { |
396 | + name: 'resource.management', | |
397 | + places: [ | |
398 | + { | |
399 | + name: 'resource.resources-library', | |
400 | + icon: 'folder', | |
401 | + path: '/resources-library' | |
402 | + } | |
403 | + ] | |
404 | + }, | |
405 | + { | |
372 | 406 | name: 'dashboard.management', |
373 | 407 | places: [ |
374 | 408 | { | ... | ... |
... | ... | @@ -34,6 +34,7 @@ import { MODULES_MAP } from '@shared/public-api'; |
34 | 34 | import { modulesMap } from '../../common/modules-map'; |
35 | 35 | import { DeviceProfileModule } from './device-profile/device-profile.module'; |
36 | 36 | import { ApiUsageModule } from '@home/pages/api-usage/api-usage.module'; |
37 | +import { ResourceModule } from '@home/pages/resource/resource.module'; | |
37 | 38 | |
38 | 39 | @NgModule({ |
39 | 40 | exports: [ |
... | ... | @@ -52,6 +53,7 @@ import { ApiUsageModule } from '@home/pages/api-usage/api-usage.module'; |
52 | 53 | DashboardModule, |
53 | 54 | AuditLogModule, |
54 | 55 | ApiUsageModule, |
56 | + ResourceModule, | |
55 | 57 | UserModule |
56 | 58 | ], |
57 | 59 | providers: [ | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2021 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { RouterModule, Routes } from '@angular/router'; | |
18 | +import { EntitiesTableComponent } from '@home/components/entity/entities-table.component'; | |
19 | +import { Authority } from '@shared/models/authority.enum'; | |
20 | +import { NgModule } from '@angular/core'; | |
21 | +import { ResourcesLibraryTableConfigResolver } from './resources-library-table-config.resolve'; | |
22 | + | |
23 | +const routes: Routes = [ | |
24 | + { | |
25 | + path: 'resources-library', | |
26 | + component: EntitiesTableComponent, | |
27 | + data: { | |
28 | + auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN], | |
29 | + title: 'resource.resources-library', | |
30 | + breadcrumb: { | |
31 | + label: 'resource.resources-library', | |
32 | + icon: 'folder' | |
33 | + } | |
34 | + }, | |
35 | + resolve: { | |
36 | + entitiesTableConfig: ResourcesLibraryTableConfigResolver | |
37 | + } | |
38 | + } | |
39 | +]; | |
40 | + | |
41 | +@NgModule({ | |
42 | + imports: [RouterModule.forChild(routes)], | |
43 | + exports: [RouterModule], | |
44 | + providers: [ | |
45 | + ResourcesLibraryTableConfigResolver | |
46 | + ] | |
47 | +}) | |
48 | +export class ResourcesLibraryRoutingModule{ } | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2021 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { NgModule } from '@angular/core'; | |
18 | +import { CommonModule } from '@angular/common'; | |
19 | +import { ResourcesLibraryRoutingModule } from '@home/pages/resource/resource-routing.module'; | |
20 | +import { SharedModule } from '@shared/shared.module'; | |
21 | +import { HomeComponentsModule } from '@home/components/home-components.module'; | |
22 | +import { ResourcesLibraryComponent } from './resources-library.component'; | |
23 | + | |
24 | +@NgModule({ | |
25 | + declarations: [ResourcesLibraryComponent], | |
26 | + imports: [ | |
27 | + CommonModule, | |
28 | + SharedModule, | |
29 | + HomeComponentsModule, | |
30 | + ResourcesLibraryRoutingModule | |
31 | + ] | |
32 | +}) | |
33 | +export class ResourceModule { } | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2021 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { Injectable } from '@angular/core'; | |
18 | +import { | |
19 | + checkBoxCell, | |
20 | + DateEntityTableColumn, | |
21 | + EntityTableColumn, | |
22 | + EntityTableConfig | |
23 | +} from '@home/models/entity/entities-table-config.models'; | |
24 | +import { Resolve } from '@angular/router'; | |
25 | +import { Resource, ResourceInfo, ResourceTypeTranslationMap } from '@shared/models/resource.models'; | |
26 | +import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models'; | |
27 | +import { Direction } from '@shared/models/page/sort-order'; | |
28 | +import { NULL_UUID } from '@shared/models/id/has-uuid'; | |
29 | +import { DatePipe } from '@angular/common'; | |
30 | +import { TranslateService } from '@ngx-translate/core'; | |
31 | +import { ResourceService } from '@core/http/resource.service'; | |
32 | +import { getCurrentAuthUser } from '@core/auth/auth.selectors'; | |
33 | +import { Store } from '@ngrx/store'; | |
34 | +import { AppState } from '@core/core.state'; | |
35 | +import { Authority } from '@shared/models/authority.enum'; | |
36 | +import { ResourcesLibraryComponent } from '@home/pages/resource/resources-library.component'; | |
37 | +import { Observable } from 'rxjs/internal/Observable'; | |
38 | +import { PageData } from '@shared/models/page/page-data'; | |
39 | + | |
40 | +@Injectable() | |
41 | +export class ResourcesLibraryTableConfigResolver implements Resolve<EntityTableConfig<Resource>> { | |
42 | + | |
43 | + private readonly config: EntityTableConfig<Resource> = new EntityTableConfig<Resource>(); | |
44 | + private readonly resourceTypesTranslationMap = ResourceTypeTranslationMap; | |
45 | + | |
46 | + constructor(private store: Store<AppState>, | |
47 | + private resourceService: ResourceService, | |
48 | + private translate: TranslateService, | |
49 | + private datePipe: DatePipe) { | |
50 | + | |
51 | + this.config.entityType = EntityType.TB_RESOURCE; | |
52 | + this.config.entityComponent = ResourcesLibraryComponent; | |
53 | + this.config.entityTranslations = entityTypeTranslations.get(EntityType.TB_RESOURCE); | |
54 | + this.config.entityResources = entityTypeResources.get(EntityType.TB_RESOURCE); | |
55 | + this.config.defaultSortOrder = {property: 'title', direction: Direction.ASC}; | |
56 | + | |
57 | + this.config.entityTitle = (resource) => resource ? | |
58 | + resource.title : ''; | |
59 | + | |
60 | + this.config.columns.push( | |
61 | + new DateEntityTableColumn<ResourceInfo>('createdTime', 'common.created-time', this.datePipe, '150px'), | |
62 | + new EntityTableColumn<ResourceInfo>('title', 'widgets-bundle.title', '60%'), | |
63 | + new EntityTableColumn<ResourceInfo>('resourceType', 'resource.resource-type', '40%', | |
64 | + entity => this.resourceTypesTranslationMap.get(entity.resourceType)), | |
65 | + new EntityTableColumn<ResourceInfo>('tenantId', 'widgets-bundle.system', '60px', | |
66 | + entity => { | |
67 | + return checkBoxCell(entity.tenantId.id === NULL_UUID); | |
68 | + }), | |
69 | + ); | |
70 | + | |
71 | + this.config.cellActionDescriptors.push( | |
72 | + { | |
73 | + name: this.translate.instant('resource.export'), | |
74 | + icon: 'file_download', | |
75 | + isEnabled: () => true, | |
76 | + onAction: ($event, entity) => this.exportResource($event, entity) | |
77 | + } | |
78 | + ); | |
79 | + | |
80 | + this.config.deleteEntityTitle = resource => this.translate.instant('resource.delete-resource-title', | |
81 | + { resourceTitle: resource.title }); | |
82 | + this.config.deleteEntityContent = () => this.translate.instant('resource.delete-resource-text'); | |
83 | + this.config.deleteEntitiesTitle = count => this.translate.instant('resource.delete-resources-title', {count}); | |
84 | + this.config.deleteEntitiesContent = () => this.translate.instant('resource.delete-resources-text'); | |
85 | + | |
86 | + this.config.entitiesFetchFunction = pageLink => this.resourceService.getResources(pageLink) as Observable<PageData<Resource>>; | |
87 | + this.config.loadEntity = id => this.resourceService.getResource(id.id); | |
88 | + this.config.saveEntity = resource => this.resourceService.saveResource(resource); | |
89 | + this.config.deleteEntity = id => this.resourceService.deleteResource(id.id); | |
90 | + } | |
91 | + | |
92 | + resolve(): EntityTableConfig<Resource> { | |
93 | + this.config.tableTitle = this.translate.instant('resource.resources-library'); | |
94 | + const authUser = getCurrentAuthUser(this.store); | |
95 | + this.config.deleteEnabled = (resource) => this.isResourceEditable(resource, authUser.authority); | |
96 | + this.config.entitySelectionEnabled = (resource) => this.isResourceEditable(resource, authUser.authority); | |
97 | + this.config.detailsReadonly = (resource) => !this.isResourceEditable(resource, authUser.authority); | |
98 | + return this.config; | |
99 | + } | |
100 | + | |
101 | + exportResource($event: Event, resource: ResourceInfo) { | |
102 | + if ($event) { | |
103 | + $event.stopPropagation(); | |
104 | + } | |
105 | + this.resourceService.downloadResource(resource.id.id).subscribe(); | |
106 | + } | |
107 | + | |
108 | + private isResourceEditable(resource: Resource, authority: Authority): boolean { | |
109 | + if (authority === Authority.TENANT_ADMIN) { | |
110 | + return resource && resource.tenantId && resource.tenantId.id !== NULL_UUID; | |
111 | + } else { | |
112 | + return authority === Authority.SYS_ADMIN; | |
113 | + } | |
114 | + } | |
115 | +} | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2021 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | +<div class="tb-details-buttons" fxLayout.xs="column"> | |
19 | + <button mat-raised-button color="primary" fxFlex.xs | |
20 | + [disabled]="(isLoading$ | async)" | |
21 | + (click)="onEntityAction($event, 'delete')" | |
22 | + [fxShow]="!hideDelete() && !isEdit"> | |
23 | + {{'resource.delete' | translate }} | |
24 | + </button> | |
25 | +</div> | |
26 | +<div class="mat-padding" fxLayout="column"> | |
27 | + <form [formGroup]="entityForm"> | |
28 | + <fieldset [disabled]="(isLoading$ | async) || !isEdit"> | |
29 | + <mat-form-field class="mat-block"> | |
30 | + <mat-label translate>resource.resource-type</mat-label> | |
31 | + <mat-select formControlName="resourceType" required> | |
32 | + <mat-option *ngFor="let resourceType of resourceTypes" [value]="resourceType"> | |
33 | + {{ resourceTypesTranslationMap.get(resourceType) }} | |
34 | + </mat-option> | |
35 | + </mat-select> | |
36 | + </mat-form-field> | |
37 | + <mat-form-field class="mat-block" *ngIf="entityForm.get('resourceType').value !== resourceType.LWM2M_MODEL || !isAdd"> | |
38 | + <mat-label translate>resource.title</mat-label> | |
39 | + <input matInput formControlName="title" required [readonly]="entityForm.get('resourceType').value === resourceType.LWM2M_MODEL"> | |
40 | + <mat-error *ngIf="entityForm.get('title').hasError('required')"> | |
41 | + {{ 'resource.title-required' | translate }} | |
42 | + </mat-error> | |
43 | + </mat-form-field> | |
44 | + <tb-file-input | |
45 | + formControlName="data" | |
46 | + required | |
47 | + [convertToBase64]="true" | |
48 | + [allowedExtensions]="getAllowedExtensions()" | |
49 | + [accept]="getAcceptType()" | |
50 | + dropLabel="{{'resource.drop-file' | translate}}" | |
51 | + [existingFileName]="entityForm.get('fileName')?.value" | |
52 | + (fileNameChanged)="entityForm?.get('fileName').patchValue($event)"> | |
53 | + </tb-file-input> | |
54 | + </fieldset> | |
55 | + </form> | |
56 | +</div> | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2021 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; | |
18 | +import { Subject } from 'rxjs'; | |
19 | +import { Store } from '@ngrx/store'; | |
20 | +import { AppState } from '@core/core.state'; | |
21 | +import { TranslateService } from '@ngx-translate/core'; | |
22 | +import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; | |
23 | +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; | |
24 | +import { EntityComponent } from '@home/components/entity/entity.component'; | |
25 | +import { | |
26 | + Resource, | |
27 | + ResourceType, | |
28 | + ResourceTypeExtension, | |
29 | + ResourceTypeMIMETypes, | |
30 | + ResourceTypeTranslationMap | |
31 | +} from '@shared/models/resource.models'; | |
32 | +import { distinctUntilChanged, takeUntil } from 'rxjs/operators'; | |
33 | + | |
34 | +@Component({ | |
35 | + selector: 'tb-resources-library', | |
36 | + templateUrl: './resources-library.component.html' | |
37 | +}) | |
38 | +export class ResourcesLibraryComponent extends EntityComponent<Resource> implements OnInit, OnDestroy { | |
39 | + | |
40 | + readonly resourceType = ResourceType; | |
41 | + readonly resourceTypes = Object.values(this.resourceType); | |
42 | + readonly resourceTypesTranslationMap = ResourceTypeTranslationMap; | |
43 | + | |
44 | + private destroy$ = new Subject(); | |
45 | + | |
46 | + constructor(protected store: Store<AppState>, | |
47 | + protected translate: TranslateService, | |
48 | + @Inject('entity') protected entityValue: Resource, | |
49 | + @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig<Resource>, | |
50 | + public fb: FormBuilder) { | |
51 | + super(store, fb, entityValue, entitiesTableConfigValue); | |
52 | + } | |
53 | + | |
54 | + ngOnInit() { | |
55 | + super.ngOnInit(); | |
56 | + this.entityForm.get('resourceType').valueChanges.pipe( | |
57 | + distinctUntilChanged((oldValue, newValue) => [oldValue, newValue].includes(this.resourceType.LWM2M_MODEL)), | |
58 | + takeUntil(this.destroy$) | |
59 | + ).subscribe((type) => { | |
60 | + if (type === this.resourceType.LWM2M_MODEL) { | |
61 | + this.entityForm.get('title').clearValidators(); | |
62 | + } else { | |
63 | + this.entityForm.get('title').setValidators(Validators.required); | |
64 | + } | |
65 | + this.entityForm.get('title').updateValueAndValidity({emitEvent: false}); | |
66 | + }); | |
67 | + } | |
68 | + | |
69 | + ngOnDestroy() { | |
70 | + super.ngOnDestroy(); | |
71 | + this.destroy$.next(); | |
72 | + this.destroy$.complete(); | |
73 | + } | |
74 | + | |
75 | + hideDelete() { | |
76 | + if (this.entitiesTableConfig) { | |
77 | + return !this.entitiesTableConfig.deleteEnabled(this.entity); | |
78 | + } else { | |
79 | + return false; | |
80 | + } | |
81 | + } | |
82 | + | |
83 | + buildForm(entity: Resource): FormGroup { | |
84 | + return this.fb.group( | |
85 | + { | |
86 | + resourceType: [{value: entity?.resourceType ? entity.resourceType : ResourceType.LWM2M_MODEL, | |
87 | + disabled: this.isEdit }, [Validators.required]], | |
88 | + data: [entity ? entity.data : null, [Validators.required]], | |
89 | + fileName: [entity ? entity.fileName : null, [Validators.required]], | |
90 | + title: [entity ? entity.title : '', []] | |
91 | + } | |
92 | + ); | |
93 | + } | |
94 | + | |
95 | + updateForm(entity: Resource) { | |
96 | + this.entityForm.patchValue({resourceType: entity.resourceType}); | |
97 | + if (this.isEdit) { | |
98 | + this.entityForm.get('resourceType').disable({emitEvent: false}); | |
99 | + } | |
100 | + this.entityForm.patchValue({ | |
101 | + data: entity.data, | |
102 | + fileName: entity.fileName, | |
103 | + title: entity.title | |
104 | + }); | |
105 | + } | |
106 | + | |
107 | + getAllowedExtensions() { | |
108 | + try { | |
109 | + return ResourceTypeExtension.get(this.entityForm.get('resourceType').value); | |
110 | + } catch (e) { | |
111 | + return ''; | |
112 | + } | |
113 | + } | |
114 | + | |
115 | + getAcceptType() { | |
116 | + try { | |
117 | + return ResourceTypeMIMETypes.get(this.entityForm.get('resourceType').value); | |
118 | + } catch (e) { | |
119 | + return '*/*'; | |
120 | + } | |
121 | + } | |
122 | +} | ... | ... |
... | ... | @@ -101,6 +101,9 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, |
101 | 101 | @Input() |
102 | 102 | existingFileName: string; |
103 | 103 | |
104 | + @Input() | |
105 | + convertToBase64 = false; | |
106 | + | |
104 | 107 | @Output() |
105 | 108 | fileNameChanged = new EventEmitter<string>(); |
106 | 109 | |
... | ... | @@ -128,7 +131,7 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, |
128 | 131 | const reader = new FileReader(); |
129 | 132 | reader.onload = (loadEvent) => { |
130 | 133 | if (typeof reader.result === 'string') { |
131 | - const fileContent = reader.result; | |
134 | + const fileContent = this.convertToBase64 ? window.btoa(reader.result) : reader.result; | |
132 | 135 | if (fileContent && fileContent.length > 0) { |
133 | 136 | if (this.contentConvertFunction) { |
134 | 137 | this.fileContent = this.contentConvertFunction(fileContent); |
... | ... | @@ -144,7 +147,11 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, |
144 | 147 | } |
145 | 148 | } |
146 | 149 | }; |
147 | - reader.readAsText(file.file); | |
150 | + if (this.convertToBase64) { | |
151 | + reader.readAsBinaryString(file.file); | |
152 | + } else { | |
153 | + reader.readAsText(file.file); | |
154 | + } | |
148 | 155 | } |
149 | 156 | } |
150 | 157 | }); |
... | ... | @@ -159,7 +166,9 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, |
159 | 166 | } |
160 | 167 | |
161 | 168 | ngOnDestroy() { |
162 | - this.autoUploadSubscription.unsubscribe(); | |
169 | + if (this.autoUploadSubscription) { | |
170 | + this.autoUploadSubscription.unsubscribe(); | |
171 | + } | |
163 | 172 | } |
164 | 173 | |
165 | 174 | registerOnChange(fn: any): void { | ... | ... |
... | ... | @@ -17,22 +17,6 @@ |
17 | 17 | import { TenantId } from './id/tenant-id'; |
18 | 18 | import { BaseData, HasId } from '@shared/models/base-data'; |
19 | 19 | |
20 | -/// | |
21 | -/// Copyright © 2016-2019 The Thingsboard Authors | |
22 | -/// | |
23 | -/// Licensed under the Apache License, Version 2.0 (the "License"); | |
24 | -/// you may not use this file except in compliance with the License. | |
25 | -/// You may obtain a copy of the License at | |
26 | -/// | |
27 | -/// http://www.apache.org/licenses/LICENSE-2.0 | |
28 | -/// | |
29 | -/// Unless required by applicable law or agreed to in writing, software | |
30 | -/// distributed under the License is distributed on an "AS IS" BASIS, | |
31 | -/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
32 | -/// See the License for the specific language governing permissions and | |
33 | -/// limitations under the License. | |
34 | -/// | |
35 | - | |
36 | 20 | export enum EntityType { |
37 | 21 | TENANT = 'TENANT', |
38 | 22 | TENANT_PROFILE = 'TENANT_PROFILE', |
... | ... | @@ -48,7 +32,8 @@ export enum EntityType { |
48 | 32 | ENTITY_VIEW = 'ENTITY_VIEW', |
49 | 33 | WIDGETS_BUNDLE = 'WIDGETS_BUNDLE', |
50 | 34 | WIDGET_TYPE = 'WIDGET_TYPE', |
51 | - API_USAGE_STATE = 'API_USAGE_STATE' | |
35 | + API_USAGE_STATE = 'API_USAGE_STATE', | |
36 | + TB_RESOURCE = 'TB_RESOURCE' | |
52 | 37 | } |
53 | 38 | |
54 | 39 | export enum AliasEntityType { |
... | ... | @@ -282,7 +267,17 @@ export const entityTypeTranslations = new Map<EntityType | AliasEntityType, Enti |
282 | 267 | type: 'entity.type-current-user-owner', |
283 | 268 | list: 'entity.type-current-user-owner' |
284 | 269 | } |
285 | - ] | |
270 | + ], | |
271 | + [ | |
272 | + EntityType.TB_RESOURCE, | |
273 | + { | |
274 | + details: 'resource.resource-library-details', | |
275 | + add: 'resource.add', | |
276 | + noEntities: 'resource.no-resource-text', | |
277 | + search: 'resource.search', | |
278 | + selectedEntities: 'resource.selected-resources' | |
279 | + } | |
280 | + ], | |
286 | 281 | ] |
287 | 282 | ); |
288 | 283 | |
... | ... | @@ -353,6 +348,12 @@ export const entityTypeResources = new Map<EntityType, EntityTypeResource<BaseDa |
353 | 348 | { |
354 | 349 | helpLinkId: 'widgetsBundles' |
355 | 350 | } |
351 | + ], | |
352 | + [ | |
353 | + EntityType.TB_RESOURCE, | |
354 | + { | |
355 | + helpLinkId: 'resources' | |
356 | + } | |
356 | 357 | ] |
357 | 358 | ] |
358 | 359 | ); | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2021 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { EntityId } from '@shared/models/id/entity-id'; | |
18 | +import { EntityType } from '@shared/models/entity-type.models'; | |
19 | + | |
20 | +export class TbResourceId implements EntityId { | |
21 | + entityType = EntityType.TB_RESOURCE; | |
22 | + id: string; | |
23 | + constructor(id: string) { | |
24 | + this.id = id; | |
25 | + } | |
26 | +} | ... | ... |
... | ... | @@ -39,6 +39,7 @@ export * from './material.models'; |
39 | 39 | export * from './oauth2.models'; |
40 | 40 | export * from './queue.models'; |
41 | 41 | export * from './relation.models'; |
42 | +export * from './resource.models'; | |
42 | 43 | export * from './rule-chain.models'; |
43 | 44 | export * from './rule-node.models'; |
44 | 45 | export * from './settings.models'; | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2021 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { BaseData } from '@shared/models/base-data'; | |
18 | +import { TenantId } from '@shared/models/id/tenant-id'; | |
19 | +import { TbResourceId } from '@shared/models/id/tb-resource-id'; | |
20 | + | |
21 | +export enum ResourceType { | |
22 | + LWM2M_MODEL = 'LWM2M_MODEL', | |
23 | + PKCS_12 = 'PKCS_12', | |
24 | + JKS = 'JKS' | |
25 | +} | |
26 | + | |
27 | +export const ResourceTypeMIMETypes = new Map<ResourceType, string>( | |
28 | + [ | |
29 | + [ResourceType.LWM2M_MODEL, 'application/xml,text/xml'], | |
30 | + [ResourceType.PKCS_12, 'application/x-pkcs12'], | |
31 | + [ResourceType.JKS, 'application/x-java-keystore'] | |
32 | + ] | |
33 | +); | |
34 | + | |
35 | +export const ResourceTypeExtension = new Map<ResourceType, string>( | |
36 | + [ | |
37 | + [ResourceType.LWM2M_MODEL, 'xml'], | |
38 | + [ResourceType.PKCS_12, 'p12,pfx'], | |
39 | + [ResourceType.JKS, 'jks'] | |
40 | + ] | |
41 | +); | |
42 | + | |
43 | +export const ResourceTypeTranslationMap = new Map<ResourceType, string>( | |
44 | + [ | |
45 | + [ResourceType.LWM2M_MODEL, 'LWM2M model'], | |
46 | + [ResourceType.PKCS_12, 'PKCS #12'], | |
47 | + [ResourceType.JKS, 'JKS'] | |
48 | + ] | |
49 | +); | |
50 | + | |
51 | +export interface ResourceInfo extends BaseData<TbResourceId> { | |
52 | + tenantId?: TenantId; | |
53 | + resourceKey?: string; | |
54 | + title?: string; | |
55 | + resourceType: ResourceType; | |
56 | +} | |
57 | + | |
58 | +export interface Resource extends ResourceInfo { | |
59 | + data: string; | |
60 | + fileName: string; | |
61 | +} | ... | ... |
... | ... | @@ -1963,6 +1963,31 @@ |
1963 | 1963 | "invalid-additional-info": "Unable to parse additional info json.", |
1964 | 1964 | "no-relations-text": "No relations found" |
1965 | 1965 | }, |
1966 | + "resource": { | |
1967 | + "add": "Add Resource", | |
1968 | + "delete": "Delete resource", | |
1969 | + "delete-resource-text": "Be careful, after the confirmation the resource will become unrecoverable.", | |
1970 | + "delete-resource-title": "Are you sure you want to delete the resource '{{resourceTitle}}'?", | |
1971 | + "delete-resources-action-title": "Delete { count, plural, 1 {1 resource} other {# resources} }", | |
1972 | + "delete-resources-text": "Be careful, after the confirmation all selected resources will be removed.", | |
1973 | + "delete-resources-title": "Are you sure you want to delete { count, plural, 1 {1 resource} other {# resources} }?", | |
1974 | + "drop-file": "Drop a resource file or click to select a file to upload.", | |
1975 | + "empty": "Resource is empty", | |
1976 | + "export": "Export resource", | |
1977 | + "management": "Resource management", | |
1978 | + "no-resource-matching": "No resource matching '{{widgetsBundle}}' were found.", | |
1979 | + "no-resource-text": "No resources found", | |
1980 | + "open-widgets-bundle": "Open widgets bundle", | |
1981 | + "resource": "Resource", | |
1982 | + "resource-library-details": "Resource library details", | |
1983 | + "resource-type": "Resource type", | |
1984 | + "resources-library": "Resources library", | |
1985 | + "search": "Search resources", | |
1986 | + "selected-resources": "{ count, plural, 1 {1 resource} other {# resources} } selected", | |
1987 | + "system": "System", | |
1988 | + "title": "Title", | |
1989 | + "title-required": "Title is required." | |
1990 | + }, | |
1966 | 1991 | "rulechain": { |
1967 | 1992 | "rulechain": "Rule chain", |
1968 | 1993 | "rulechains": "Rule chains", | ... | ... |