From b033b51712244d08e0f5e0beb8be60c9f8fa4cd2 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 23 Mar 2021 18:51:53 +0200 Subject: [PATCH] UI: Added manager resources; added new entity type TB_RESOURCE --- ui-ngx/src/app/core/http/resource.service.ts | 81 ++++++++++++ ui-ngx/src/app/core/services/menu.service.ts | 34 +++++ .../modules/home/pages/home-pages.module.ts | 2 + .../pages/resource/resource-routing.module.ts | 48 +++++++ .../home/pages/resource/resource.module.ts | 33 +++++ .../resources-library-table-config.resolve.ts | 115 +++++++++++++++++ .../resource/resources-library.component.html | 56 ++++++++ .../resource/resources-library.component.ts | 122 ++++++++++++++++++ .../shared/components/file-input.component.ts | 15 ++- .../app/shared/models/entity-type.models.ts | 37 +++--- .../app/shared/models/id/tb-resource-id.ts | 26 ++++ ui-ngx/src/app/shared/models/public-api.ts | 1 + .../src/app/shared/models/resource.models.ts | 61 +++++++++ .../assets/locale/locale.constant-en_US.json | 25 ++++ 14 files changed, 635 insertions(+), 21 deletions(-) create mode 100644 ui-ngx/src/app/core/http/resource.service.ts create mode 100644 ui-ngx/src/app/modules/home/pages/resource/resource-routing.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/resource/resource.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/resource/resources-library-table-config.resolve.ts create mode 100644 ui-ngx/src/app/modules/home/pages/resource/resources-library.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/resource/resources-library.component.ts create mode 100644 ui-ngx/src/app/shared/models/id/tb-resource-id.ts create mode 100644 ui-ngx/src/app/shared/models/resource.models.ts diff --git a/ui-ngx/src/app/core/http/resource.service.ts b/ui-ngx/src/app/core/http/resource.service.ts new file mode 100644 index 0000000000..d8bc18291b --- /dev/null +++ b/ui-ngx/src/app/core/http/resource.service.ts @@ -0,0 +1,81 @@ +/// +/// Copyright © 2016-2021 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 { HttpClient } from '@angular/common/http'; +import { PageLink } from '@shared/models/page/page-link'; +import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils'; +import { Observable } from 'rxjs'; +import { PageData } from '@shared/models/page/page-data'; +import { Resource, ResourceInfo } from '@shared/models/resource.models'; +import { map } from 'rxjs/operators'; + +@Injectable({ + providedIn: 'root' +}) +export class ResourceService { + constructor( + private http: HttpClient + ) { + + } + + public getResources(pageLink: PageLink, config?: RequestConfig): Observable> { + return this.http.get>(`/api/resource${pageLink.toQuery()}`, + defaultHttpOptionsFromConfig(config)); + } + + public getResource(resourceId: string, config?: RequestConfig): Observable { + return this.http.get(`/api/resource/${resourceId}`, defaultHttpOptionsFromConfig(config)); + } + + public downloadResource(resourceId: string): Observable { + return this.http.get(`/api/resource/${resourceId}/download`, { responseType: 'arraybuffer', observe: 'response' }).pipe( + map((response) => { + const headers = response.headers; + const filename = headers.get('x-filename'); + const contentType = headers.get('content-type'); + const linkElement = document.createElement('a'); + try { + const blob = new Blob([response.body], { type: contentType }); + const url = URL.createObjectURL(blob); + linkElement.setAttribute('href', url); + linkElement.setAttribute('download', filename); + const clickEvent = new MouseEvent('click', + { + view: window, + bubbles: true, + cancelable: false + } + ); + linkElement.dispatchEvent(clickEvent); + return null; + } catch (e) { + throw e; + } + }) + ); + } + + public saveResource(resource: Resource, config?: RequestConfig): Observable { + return this.http.post('/api/resource', resource, defaultHttpOptionsFromConfig(config)); + } + + public deleteResource(resourceId: string, config?: RequestConfig) { + return this.http.delete(`/api/resource/${resourceId}`, defaultHttpOptionsFromConfig(config)); + } + +} diff --git a/ui-ngx/src/app/core/services/menu.service.ts b/ui-ngx/src/app/core/services/menu.service.ts index d1fffa3fde..7783fdd2c3 100644 --- a/ui-ngx/src/app/core/services/menu.service.ts +++ b/ui-ngx/src/app/core/services/menu.service.ts @@ -103,6 +103,13 @@ export class MenuService { path: '/widgets-bundles', icon: 'now_widgets' }, + { + id: guid(), + name: 'resource.resources-library', + type: 'link', + path: '/resources-library', + icon: 'folder' + }, { id: guid(), name: 'admin.system-settings', @@ -181,6 +188,16 @@ export class MenuService { } ] }, + { + name: 'resource.management', + places: [ + { + name: 'resource.resources-library', + icon: 'folder', + path: '/resources-library' + } + ] + }, { name: 'admin.system-settings', places: [ @@ -283,6 +300,13 @@ export class MenuService { path: '/dashboards', icon: 'dashboards' }, + { + id: guid(), + name: 'resource.resources-library', + type: 'link', + path: '/resources-library', + icon: 'folder' + }, { id: guid(), name: 'admin.home-settings', @@ -368,6 +392,16 @@ export class MenuService { } ] }, + { + name: 'resource.management', + places: [ + { + name: 'resource.resources-library', + icon: 'folder', + path: '/resources-library' + } + ] + }, { name: 'dashboard.management', places: [ diff --git a/ui-ngx/src/app/modules/home/pages/home-pages.module.ts b/ui-ngx/src/app/modules/home/pages/home-pages.module.ts index a509c22dd5..d823c36bc8 100644 --- a/ui-ngx/src/app/modules/home/pages/home-pages.module.ts +++ b/ui-ngx/src/app/modules/home/pages/home-pages.module.ts @@ -34,6 +34,7 @@ import { MODULES_MAP } from '@shared/public-api'; import { modulesMap } from '../../common/modules-map'; import { DeviceProfileModule } from './device-profile/device-profile.module'; import { ApiUsageModule } from '@home/pages/api-usage/api-usage.module'; +import { ResourceModule } from '@home/pages/resource/resource.module'; @NgModule({ exports: [ @@ -52,6 +53,7 @@ import { ApiUsageModule } from '@home/pages/api-usage/api-usage.module'; DashboardModule, AuditLogModule, ApiUsageModule, + ResourceModule, UserModule ], providers: [ diff --git a/ui-ngx/src/app/modules/home/pages/resource/resource-routing.module.ts b/ui-ngx/src/app/modules/home/pages/resource/resource-routing.module.ts new file mode 100644 index 0000000000..4eab81e2f2 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/resource/resource-routing.module.ts @@ -0,0 +1,48 @@ +/// +/// Copyright © 2016-2021 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 { RouterModule, Routes } from '@angular/router'; +import { EntitiesTableComponent } from '@home/components/entity/entities-table.component'; +import { Authority } from '@shared/models/authority.enum'; +import { NgModule } from '@angular/core'; +import { ResourcesLibraryTableConfigResolver } from './resources-library-table-config.resolve'; + +const routes: Routes = [ + { + path: 'resources-library', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN], + title: 'resource.resources-library', + breadcrumb: { + label: 'resource.resources-library', + icon: 'folder' + } + }, + resolve: { + entitiesTableConfig: ResourcesLibraryTableConfigResolver + } + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], + providers: [ + ResourcesLibraryTableConfigResolver + ] +}) +export class ResourcesLibraryRoutingModule{ } diff --git a/ui-ngx/src/app/modules/home/pages/resource/resource.module.ts b/ui-ngx/src/app/modules/home/pages/resource/resource.module.ts new file mode 100644 index 0000000000..4cf31af12c --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/resource/resource.module.ts @@ -0,0 +1,33 @@ +/// +/// Copyright © 2016-2021 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 { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { ResourcesLibraryRoutingModule } from '@home/pages/resource/resource-routing.module'; +import { SharedModule } from '@shared/shared.module'; +import { HomeComponentsModule } from '@home/components/home-components.module'; +import { ResourcesLibraryComponent } from './resources-library.component'; + +@NgModule({ + declarations: [ResourcesLibraryComponent], + imports: [ + CommonModule, + SharedModule, + HomeComponentsModule, + ResourcesLibraryRoutingModule + ] +}) +export class ResourceModule { } diff --git a/ui-ngx/src/app/modules/home/pages/resource/resources-library-table-config.resolve.ts b/ui-ngx/src/app/modules/home/pages/resource/resources-library-table-config.resolve.ts new file mode 100644 index 0000000000..55dc474a3d --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/resource/resources-library-table-config.resolve.ts @@ -0,0 +1,115 @@ +/// +/// Copyright © 2016-2021 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 { + checkBoxCell, + DateEntityTableColumn, + EntityTableColumn, + EntityTableConfig +} from '@home/models/entity/entities-table-config.models'; +import { Resolve } from '@angular/router'; +import { Resource, ResourceInfo, ResourceTypeTranslationMap } from '@shared/models/resource.models'; +import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models'; +import { Direction } from '@shared/models/page/sort-order'; +import { NULL_UUID } from '@shared/models/id/has-uuid'; +import { DatePipe } from '@angular/common'; +import { TranslateService } from '@ngx-translate/core'; +import { ResourceService } from '@core/http/resource.service'; +import { getCurrentAuthUser } from '@core/auth/auth.selectors'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Authority } from '@shared/models/authority.enum'; +import { ResourcesLibraryComponent } from '@home/pages/resource/resources-library.component'; +import { Observable } from 'rxjs/internal/Observable'; +import { PageData } from '@shared/models/page/page-data'; + +@Injectable() +export class ResourcesLibraryTableConfigResolver implements Resolve> { + + private readonly config: EntityTableConfig = new EntityTableConfig(); + private readonly resourceTypesTranslationMap = ResourceTypeTranslationMap; + + constructor(private store: Store, + private resourceService: ResourceService, + private translate: TranslateService, + private datePipe: DatePipe) { + + this.config.entityType = EntityType.TB_RESOURCE; + this.config.entityComponent = ResourcesLibraryComponent; + this.config.entityTranslations = entityTypeTranslations.get(EntityType.TB_RESOURCE); + this.config.entityResources = entityTypeResources.get(EntityType.TB_RESOURCE); + this.config.defaultSortOrder = {property: 'title', direction: Direction.ASC}; + + this.config.entityTitle = (resource) => resource ? + resource.title : ''; + + this.config.columns.push( + new DateEntityTableColumn('createdTime', 'common.created-time', this.datePipe, '150px'), + new EntityTableColumn('title', 'widgets-bundle.title', '60%'), + new EntityTableColumn('resourceType', 'resource.resource-type', '40%', + entity => this.resourceTypesTranslationMap.get(entity.resourceType)), + new EntityTableColumn('tenantId', 'widgets-bundle.system', '60px', + entity => { + return checkBoxCell(entity.tenantId.id === NULL_UUID); + }), + ); + + this.config.cellActionDescriptors.push( + { + name: this.translate.instant('resource.export'), + icon: 'file_download', + isEnabled: () => true, + onAction: ($event, entity) => this.exportResource($event, entity) + } + ); + + this.config.deleteEntityTitle = resource => this.translate.instant('resource.delete-resource-title', + { resourceTitle: resource.title }); + this.config.deleteEntityContent = () => this.translate.instant('resource.delete-resource-text'); + this.config.deleteEntitiesTitle = count => this.translate.instant('resource.delete-resources-title', {count}); + this.config.deleteEntitiesContent = () => this.translate.instant('resource.delete-resources-text'); + + this.config.entitiesFetchFunction = pageLink => this.resourceService.getResources(pageLink) as Observable>; + this.config.loadEntity = id => this.resourceService.getResource(id.id); + this.config.saveEntity = resource => this.resourceService.saveResource(resource); + this.config.deleteEntity = id => this.resourceService.deleteResource(id.id); + } + + resolve(): EntityTableConfig { + this.config.tableTitle = this.translate.instant('resource.resources-library'); + const authUser = getCurrentAuthUser(this.store); + this.config.deleteEnabled = (resource) => this.isResourceEditable(resource, authUser.authority); + this.config.entitySelectionEnabled = (resource) => this.isResourceEditable(resource, authUser.authority); + this.config.detailsReadonly = (resource) => !this.isResourceEditable(resource, authUser.authority); + return this.config; + } + + exportResource($event: Event, resource: ResourceInfo) { + if ($event) { + $event.stopPropagation(); + } + this.resourceService.downloadResource(resource.id.id).subscribe(); + } + + private isResourceEditable(resource: Resource, authority: Authority): boolean { + if (authority === Authority.TENANT_ADMIN) { + return resource && resource.tenantId && resource.tenantId.id !== NULL_UUID; + } else { + return authority === Authority.SYS_ADMIN; + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/resource/resources-library.component.html b/ui-ngx/src/app/modules/home/pages/resource/resources-library.component.html new file mode 100644 index 0000000000..a971fb7afb --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/resource/resources-library.component.html @@ -0,0 +1,56 @@ + +
+ +
+
+
+
+ + resource.resource-type + + + {{ resourceTypesTranslationMap.get(resourceType) }} + + + + + resource.title + + + {{ 'resource.title-required' | translate }} + + + + +
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/resource/resources-library.component.ts b/ui-ngx/src/app/modules/home/pages/resource/resources-library.component.ts new file mode 100644 index 0000000000..560b8a3d43 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/resource/resources-library.component.ts @@ -0,0 +1,122 @@ +/// +/// Copyright © 2016-2021 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, Inject, OnDestroy, OnInit } from '@angular/core'; +import { Subject } from 'rxjs'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { TranslateService } from '@ngx-translate/core'; +import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { EntityComponent } from '@home/components/entity/entity.component'; +import { + Resource, + ResourceType, + ResourceTypeExtension, + ResourceTypeMIMETypes, + ResourceTypeTranslationMap +} from '@shared/models/resource.models'; +import { distinctUntilChanged, takeUntil } from 'rxjs/operators'; + +@Component({ + selector: 'tb-resources-library', + templateUrl: './resources-library.component.html' +}) +export class ResourcesLibraryComponent extends EntityComponent implements OnInit, OnDestroy { + + readonly resourceType = ResourceType; + readonly resourceTypes = Object.values(this.resourceType); + readonly resourceTypesTranslationMap = ResourceTypeTranslationMap; + + private destroy$ = new Subject(); + + constructor(protected store: Store, + protected translate: TranslateService, + @Inject('entity') protected entityValue: Resource, + @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig, + public fb: FormBuilder) { + super(store, fb, entityValue, entitiesTableConfigValue); + } + + ngOnInit() { + super.ngOnInit(); + this.entityForm.get('resourceType').valueChanges.pipe( + distinctUntilChanged((oldValue, newValue) => [oldValue, newValue].includes(this.resourceType.LWM2M_MODEL)), + takeUntil(this.destroy$) + ).subscribe((type) => { + if (type === this.resourceType.LWM2M_MODEL) { + this.entityForm.get('title').clearValidators(); + } else { + this.entityForm.get('title').setValidators(Validators.required); + } + this.entityForm.get('title').updateValueAndValidity({emitEvent: false}); + }); + } + + ngOnDestroy() { + super.ngOnDestroy(); + this.destroy$.next(); + this.destroy$.complete(); + } + + hideDelete() { + if (this.entitiesTableConfig) { + return !this.entitiesTableConfig.deleteEnabled(this.entity); + } else { + return false; + } + } + + buildForm(entity: Resource): FormGroup { + return this.fb.group( + { + resourceType: [{value: entity?.resourceType ? entity.resourceType : ResourceType.LWM2M_MODEL, + disabled: this.isEdit }, [Validators.required]], + data: [entity ? entity.data : null, [Validators.required]], + fileName: [entity ? entity.fileName : null, [Validators.required]], + title: [entity ? entity.title : '', []] + } + ); + } + + updateForm(entity: Resource) { + this.entityForm.patchValue({resourceType: entity.resourceType}); + if (this.isEdit) { + this.entityForm.get('resourceType').disable({emitEvent: false}); + } + this.entityForm.patchValue({ + data: entity.data, + fileName: entity.fileName, + title: entity.title + }); + } + + getAllowedExtensions() { + try { + return ResourceTypeExtension.get(this.entityForm.get('resourceType').value); + } catch (e) { + return ''; + } + } + + getAcceptType() { + try { + return ResourceTypeMIMETypes.get(this.entityForm.get('resourceType').value); + } catch (e) { + return '*/*'; + } + } +} diff --git a/ui-ngx/src/app/shared/components/file-input.component.ts b/ui-ngx/src/app/shared/components/file-input.component.ts index 1dc64194d9..b91fba1def 100644 --- a/ui-ngx/src/app/shared/components/file-input.component.ts +++ b/ui-ngx/src/app/shared/components/file-input.component.ts @@ -101,6 +101,9 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, @Input() existingFileName: string; + @Input() + convertToBase64 = false; + @Output() fileNameChanged = new EventEmitter(); @@ -128,7 +131,7 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, const reader = new FileReader(); reader.onload = (loadEvent) => { if (typeof reader.result === 'string') { - const fileContent = reader.result; + const fileContent = this.convertToBase64 ? window.btoa(reader.result) : reader.result; if (fileContent && fileContent.length > 0) { if (this.contentConvertFunction) { this.fileContent = this.contentConvertFunction(fileContent); @@ -144,7 +147,11 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, } } }; - reader.readAsText(file.file); + if (this.convertToBase64) { + reader.readAsBinaryString(file.file); + } else { + reader.readAsText(file.file); + } } } }); @@ -159,7 +166,9 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, } ngOnDestroy() { - this.autoUploadSubscription.unsubscribe(); + if (this.autoUploadSubscription) { + this.autoUploadSubscription.unsubscribe(); + } } registerOnChange(fn: any): void { diff --git a/ui-ngx/src/app/shared/models/entity-type.models.ts b/ui-ngx/src/app/shared/models/entity-type.models.ts index 833cf5ccae..846aa4e6dc 100644 --- a/ui-ngx/src/app/shared/models/entity-type.models.ts +++ b/ui-ngx/src/app/shared/models/entity-type.models.ts @@ -17,22 +17,6 @@ import { TenantId } from './id/tenant-id'; import { BaseData, HasId } from '@shared/models/base-data'; -/// -/// Copyright © 2016-2019 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 enum EntityType { TENANT = 'TENANT', TENANT_PROFILE = 'TENANT_PROFILE', @@ -48,7 +32,8 @@ export enum EntityType { ENTITY_VIEW = 'ENTITY_VIEW', WIDGETS_BUNDLE = 'WIDGETS_BUNDLE', WIDGET_TYPE = 'WIDGET_TYPE', - API_USAGE_STATE = 'API_USAGE_STATE' + API_USAGE_STATE = 'API_USAGE_STATE', + TB_RESOURCE = 'TB_RESOURCE' } export enum AliasEntityType { @@ -282,7 +267,17 @@ export const entityTypeTranslations = new Map( + [ + [ResourceType.LWM2M_MODEL, 'application/xml,text/xml'], + [ResourceType.PKCS_12, 'application/x-pkcs12'], + [ResourceType.JKS, 'application/x-java-keystore'] + ] +); + +export const ResourceTypeExtension = new Map( + [ + [ResourceType.LWM2M_MODEL, 'xml'], + [ResourceType.PKCS_12, 'p12,pfx'], + [ResourceType.JKS, 'jks'] + ] +); + +export const ResourceTypeTranslationMap = new Map( + [ + [ResourceType.LWM2M_MODEL, 'LWM2M model'], + [ResourceType.PKCS_12, 'PKCS #12'], + [ResourceType.JKS, 'JKS'] + ] +); + +export interface ResourceInfo extends BaseData { + tenantId?: TenantId; + resourceKey?: string; + title?: string; + resourceType: ResourceType; +} + +export interface Resource extends ResourceInfo { + data: string; + fileName: string; +} diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 995d0cd50b..e7e301b49d 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1963,6 +1963,31 @@ "invalid-additional-info": "Unable to parse additional info json.", "no-relations-text": "No relations found" }, + "resource": { + "add": "Add Resource", + "delete": "Delete resource", + "delete-resource-text": "Be careful, after the confirmation the resource will become unrecoverable.", + "delete-resource-title": "Are you sure you want to delete the resource '{{resourceTitle}}'?", + "delete-resources-action-title": "Delete { count, plural, 1 {1 resource} other {# resources} }", + "delete-resources-text": "Be careful, after the confirmation all selected resources will be removed.", + "delete-resources-title": "Are you sure you want to delete { count, plural, 1 {1 resource} other {# resources} }?", + "drop-file": "Drop a resource file or click to select a file to upload.", + "empty": "Resource is empty", + "export": "Export resource", + "management": "Resource management", + "no-resource-matching": "No resource matching '{{widgetsBundle}}' were found.", + "no-resource-text": "No resources found", + "open-widgets-bundle": "Open widgets bundle", + "resource": "Resource", + "resource-library-details": "Resource library details", + "resource-type": "Resource type", + "resources-library": "Resources library", + "search": "Search resources", + "selected-resources": "{ count, plural, 1 {1 resource} other {# resources} } selected", + "system": "System", + "title": "Title", + "title-required": "Title is required." + }, "rulechain": { "rulechain": "Rule chain", "rulechains": "Rule chains",