UI: General resources

This commit is contained in:
ArtemDzhereleiko 2025-09-10 09:41:47 +03:00
parent c3d40a61e8
commit cd6063fd09
12 changed files with 77 additions and 50 deletions

View File

@ -299,9 +299,7 @@ export class EntityService {
entityIds);
break;
case EntityType.TB_RESOURCE:
observable = this.getEntitiesByIdsObservable(
(id) => this.resourceService.getResource(id, config),
entityIds);
observable = this.resourceService.getResourcesByIds(entityIds, config);
break;
}
return observable;
@ -478,7 +476,7 @@ export class EntityService {
break;
case EntityType.TB_RESOURCE:
pageLink.sortOrder.property = 'title';
entitiesObservable = this.resourceService.getTenantResources(pageLink, subType as ResourceType, config);
entitiesObservable = this.resourceService.getResources(pageLink, subType as ResourceType, null, config);
break;
case EntityType.QUEUE_STATS:
pageLink.sortOrder.property = 'createdTime';

View File

@ -24,6 +24,7 @@ import { Resource, ResourceInfo, ResourceSubType, ResourceType, TBResourceScope
import { catchError, mergeMap } from 'rxjs/operators';
import { isNotEmptyStr } from '@core/utils';
import { ResourcesService } from '@core/services/resources.service';
import { NotificationTarget } from "@shared/models/notification.models";
@Injectable({
providedIn: 'root'
@ -47,12 +48,8 @@ export class ResourceService {
return this.http.get<PageData<ResourceInfo>>(url, defaultHttpOptionsFromConfig(config));
}
public getTenantResources(pageLink: PageLink, resourceType?: ResourceType, config?: RequestConfig): Observable<PageData<ResourceInfo>> {
let url = `/api/resource${pageLink.toQuery()}`;
if (isNotEmptyStr(resourceType)) {
url += `&resourceType=${resourceType}`;
}
return this.http.get<PageData<ResourceInfo>>(url, defaultHttpOptionsFromConfig(config));
public getTenantResources(pageLink: PageLink, config?: RequestConfig): Observable<PageData<ResourceInfo>> {
return this.http.get<PageData<ResourceInfo>>(`/api/resource/tenant${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config))
}
public getResource(resourceId: string, config?: RequestConfig): Observable<Resource> {
@ -98,4 +95,9 @@ export class ResourceService {
return this.http.delete(`/api/resource/${resourceId}?force=${force}`, defaultHttpOptionsFromConfig(config));
}
public getResourcesByIds(ids: string[], config?: RequestConfig): Observable<Array<ResourceInfo>> {
return this.http.get<Array<ResourceInfo>>(`/api/resource?resourceIds=${ids.join(',')}`,
defaultHttpOptionsFromConfig(config));
}
}

View File

@ -32,8 +32,8 @@
<tb-resources-library #resourcesComponent
[standalone]="true"
[entity]="resources"
[defaultResourceType]="ResourceType.TEXT"
[resourceTypes]="[ResourceType.TEXT]"
[defaultResourceType]="ResourceType.GENERAL"
[resourceTypes]="[ResourceType.GENERAL]"
[isEdit]="true">
</tb-resources-library>
</div>

View File

@ -106,6 +106,9 @@ export class ResourcesDialogComponent extends DialogComponent<ResourcesDialogCom
map((response) => response[0])
).subscribe(result => this.dialogRef.close(result));
} else {
if (resource.resourceType !== ResourceType.GENERAL) {
delete resource.descriptor;
}
this.resourceService.saveResource(resource).subscribe(result => this.dialogRef.close(result));
}
}

View File

@ -48,14 +48,16 @@
<div class="mat-padding flex flex-col">
<form [formGroup]="entityForm">
<fieldset [disabled]="(isLoading$ | async) || !isEdit">
<mat-form-field class="mat-block">
<mat-label translate>resource.resource-type</mat-label>
<mat-select formControlName="resourceType" required>
<mat-option *ngFor="let resourceType of resourceTypes" [value]="resourceType">
{{ resourceTypesTranslationMap.get(resourceType) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
@if (resourceTypes.length > 1) {
<mat-form-field class="mat-block">
<mat-label translate>resource.resource-type</mat-label>
<mat-select formControlName="resourceType" required>
<mat-option *ngFor="let resourceType of resourceTypes" [value]="resourceType">
{{ resourceTypesTranslationMap.get(resourceType) | translate }}
</mat-option>
</mat-select>
</mat-form-field>
}
<mat-form-field class="mat-block" *ngIf="entityForm.get('resourceType').value !== resourceType.LWM2M_MODEL || !isAdd">
<mat-label translate>resource.title</mat-label>
<input matInput formControlName="title" required>
@ -66,26 +68,29 @@
{{ 'resource.title-max-length' | translate }}
</mat-error>
</mat-form-field>
<tb-file-input *ngIf="isAdd"
formControlName="data"
required
label="{{ (entityForm.get('resourceType').value === resourceType.LWM2M_MODEL ? 'resource.resource-files' : 'resource.resource-file') | translate }}"
[readAsBinary]="true"
[maxSizeByte]="maxResourceSize"
[allowedExtensions]="getAllowedExtensions()"
[contentConvertFunction]="convertToBase64File"
[accept]="getAcceptType()"
[multipleFile]="entityForm.get('resourceType').value === resourceType.LWM2M_MODEL"
dropLabel="{{'resource.drop-resource-file-or' | translate}}"
[existingFileName]="entityForm.get('fileName')?.value"
(fileNameChanged)="entityForm?.get('fileName').patchValue($event)">
</tb-file-input>
<div *ngIf="!isAdd" class="flex flex-row xs:flex-col sm:gap-2 md:flex-col gt-md:gap-2">
<mat-form-field class="flex-1">
<mat-label translate>resource.file-name</mat-label>
<input matInput formControlName="fileName" type="text">
</mat-form-field>
</div>
@if (isAdd || ((isAdd || isEdit) && entityForm.get('resourceType').value === resourceType.GENERAL)) {
<tb-file-input formControlName="data"
required
label="{{ (entityForm.get('resourceType').value === resourceType.LWM2M_MODEL ? 'resource.resource-files' : 'resource.resource-file') | translate }}"
[readAsBinary]="true"
[maxSizeByte]="maxResourceSize"
[allowedExtensions]="getAllowedExtensions()"
[contentConvertFunction]="convertToBase64File"
[accept]="getAcceptType()"
[multipleFile]="entityForm.get('resourceType').value === resourceType.LWM2M_MODEL"
dropLabel="{{'resource.drop-resource-file-or' | translate}}"
[existingFileName]="entityForm.get('fileName')?.value"
(mediaTypeChanged)="mediaTypeChange($event)"
(fileNameChanged)="entityForm?.get('fileName').patchValue($event)">
</tb-file-input>
} @else {
<div class="flex flex-row xs:flex-col sm:gap-2 md:flex-col gt-md:gap-2">
<mat-form-field class="flex-1">
<mat-label translate>resource.file-name</mat-label>
<input matInput formControlName="fileName" type="text">
</mat-form-field>
</div>
}
</fieldset>
</form>
</div>

View File

@ -44,7 +44,7 @@ export class ResourcesLibraryComponent extends EntityComponent<Resource> impleme
standalone = false;
@Input()
resourceTypes = [ResourceType.LWM2M_MODEL, ResourceType.PKCS_12, ResourceType.JKS, ResourceType.TEXT];
resourceTypes = [ResourceType.LWM2M_MODEL, ResourceType.PKCS_12, ResourceType.JKS, ResourceType.GENERAL];
@Input()
defaultResourceType = ResourceType.LWM2M_MODEL;
@ -90,10 +90,19 @@ export class ResourcesLibraryComponent extends EntityComponent<Resource> impleme
title: [entity ? entity.title : '', [Validators.required, Validators.maxLength(255)]],
resourceType: [entity?.resourceType ? entity.resourceType : ResourceType.LWM2M_MODEL, Validators.required],
fileName: [entity ? entity.fileName : null, Validators.required],
data: [entity ? entity.data : null, this.isAdd ? [Validators.required] : []]
data: [entity ? entity.data : null, this.isAdd ? [Validators.required] : []],
descriptor: this.fb.group({
mediaType: ['']
})
});
}
mediaTypeChange(mediaType: string): void {
if (this.entityForm.get('resourceType').value === ResourceType.GENERAL) {
this.entityForm.get('descriptor').get('mediaType').patchValue(mediaType);
}
}
updateForm(entity: Resource): void {
this.entityForm.patchValue(entity);
}

View File

@ -76,7 +76,7 @@
placeholderText="{{ 'rule-node-config.ai.ai-resources' | translate }}"
[inlineField]="true"
[entityType]="EntityType.TB_RESOURCE"
[subType]="ResourceType.TEXT"
[subType]="ResourceType.GENERAL"
(createNew)="createAiResources($event, 'resourceIds')"
formControlName="resourceIds">
</tb-entity-list>

View File

@ -129,7 +129,7 @@ export class AiConfigComponent extends RuleNodeConfigurationComponent {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
resources: {title: name, resourceType: ResourceType.TEXT},
resources: {title: name, resourceType: ResourceType.GENERAL},
isAdd: true
}
}).afterClosed()

View File

@ -28,7 +28,7 @@ import { PageLink } from '@shared/models/page/page-link';
})
export class ResourcesTableHeaderComponent extends EntityTableHeaderComponent<Resource, PageLink, ResourceInfo> {
readonly resourceTypes = [ResourceType.LWM2M_MODEL, ResourceType.PKCS_12, ResourceType.JKS, ResourceType.TEXT];
readonly resourceTypes = [ResourceType.LWM2M_MODEL, ResourceType.PKCS_12, ResourceType.JKS, ResourceType.GENERAL];
readonly resourceTypesTranslationMap = ResourceTypeTranslationMap;
constructor(protected store: Store<AppState>) {

View File

@ -129,10 +129,15 @@ export class FileInputComponent extends PageComponent implements AfterViewInit,
@Output()
fileNameChanged = new EventEmitter<string|string[]>();
@Output()
mediaTypeChanged = new EventEmitter<string>();
fileName: string | string[];
fileContent: any;
files: File[];
mediaType: string;
@ViewChild('flow', {static: true})
flow: FlowDirective;
@ -180,6 +185,7 @@ export class FileInputComponent extends PageComponent implements AfterViewInit,
this.fileContent = files[0].fileContent;
this.fileName = files[0].fileName;
this.files = files[0].files;
this.mediaType = files[0].mediaType;
this.updateModel();
} else if (files.length > 1) {
this.fileContent = files.map(content => content.fileContent);
@ -203,6 +209,7 @@ export class FileInputComponent extends PageComponent implements AfterViewInit,
let fileName = null;
let fileContent = null;
let files = null;
let mediaType = null;
if (reader.readyState === reader.DONE) {
if (!this.workFromFileObj) {
fileContent = reader.result;
@ -211,16 +218,18 @@ export class FileInputComponent extends PageComponent implements AfterViewInit,
fileContent = this.contentConvertFunction(fileContent);
}
fileName = fileContent ? file.name : null;
mediaType = file?.file?.type || null;
}
} else if (file.name || file.file){
files = file.file;
fileName = file.name;
mediaType = file.file.type || null;
}
}
resolve({fileContent, fileName, files});
resolve({fileContent, fileName, files, mediaType});
};
reader.onerror = () => {
resolve({fileContent: null, fileName: null, files: null});
resolve({fileContent: null, fileName: null, files: null, mediaType: null});
};
if (this.readAsBinary) {
reader.readAsBinaryString(file.file);
@ -283,6 +292,7 @@ export class FileInputComponent extends PageComponent implements AfterViewInit,
this.propagateChange(this.files);
} else {
this.propagateChange(this.fileContent);
this.mediaTypeChanged.emit(this.mediaType);
this.fileNameChanged.emit(this.fileName);
}
}

View File

@ -25,7 +25,7 @@ export enum ResourceType {
PKCS_12 = 'PKCS_12',
JKS = 'JKS',
JS_MODULE = 'JS_MODULE',
TEXT = 'TEXT',
GENERAL = 'GENERAL',
}
export enum ResourceSubType {
@ -59,7 +59,7 @@ export const ResourceTypeTranslationMap = new Map<ResourceType, string>(
[ResourceType.PKCS_12, 'resource.type.pkcs-12'],
[ResourceType.JKS, 'resource.type.jks'],
[ResourceType.JS_MODULE, 'resource.type.js-module'],
[ResourceType.TEXT, 'resource.type.text'],
[ResourceType.GENERAL, 'resource.type.general'],
]
);

View File

@ -4489,7 +4489,7 @@
"js-module": "JS module",
"lwm2m-model": "LWM2M model",
"pkcs-12": "PKCS #12",
"text": "Text"
"general": "General"
},
"resource-sub-type": "Sub-type",
"sub-type": {