Open-source IoT Platform - Device management, data collection, processing and visualization.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

210 lines
8.2 KiB

///
/// Copyright © 2016-2026 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 {
DateEntityTableColumn,
EntityTableColumn,
EntityTableConfig,
CellActionDescriptor
} from '@home/models/entity/entities-table-config.models';
import { EntityType, EntityTypeResource, entityTypeTranslations } from '@shared/models/entity-type.models';
import { Direction } from '@shared/models/page/sort-order';
import { TranslateService } from '@ngx-translate/core';
import { MatDialog } from '@angular/material/dialog';
import { Injectable, Renderer2, ViewContainerRef } from '@angular/core';
import { DatePipe } from '@angular/common';
import { Observable } from 'rxjs';
import { ApiKeyInfo, ApiKey } from '@shared/models/api-key.models';
import { ApiKeyService } from '@core/http/api-key.service';
import { CustomTranslatePipe } from '@shared/pipe/custom-translate.pipe';
import { TbPopoverService } from '@shared/components/popover.service';
import { map } from 'rxjs/operators';
import { UserId } from '@shared/models/id/user-id';
import { AddApiKeyDialogComponent } from '@home/components/api-key/add-api-key-dialog.component';
import { EditApiKeyDescriptionPanelComponent } from '@home/components/api-key/edit-api-key-description-panel.component';
import { ApiKeysTableDialogData } from '@home/components/api-key/api-keys-table-dialog.component';
import {
ApiKeyGeneratedDialogComponent,
ApiKeyGeneratedDialogData
} from '@home/components/api-key/api-key-generated-dialog.component';
@Injectable()
export class ApiKeysTableConfig extends EntityTableConfig<ApiKeyInfo> {
constructor(
private apiKeyService: ApiKeyService,
private translate: TranslateService,
private customTranslate: CustomTranslatePipe,
private dialog: MatDialog,
private datePipe: DatePipe,
private popoverService: TbPopoverService,
private renderer: Renderer2,
private viewContainerRef: ViewContainerRef,
private userId: UserId,
) {
super();
this.entityType = EntityType.API_KEY;
this.detailsPanelEnabled = false;
this.pageMode = false;
this.entityTranslations = entityTypeTranslations.get(EntityType.API_KEY);
this.entityResources = {} as EntityTypeResource<ApiKeyInfo>;
this.tableTitle = this.translate.instant('api-key.api-keys');
this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC};
this.entitiesFetchFunction = pageLink => this.apiKeyService.getUserApiKeys(this.userId.id, pageLink);
this.addEntity = () => this.addApiKey();
this.deleteEntityTitle = entity => this.translate.instant('api-key.delete-api-key-title', {name: entity.description});
this.deleteEntityContent = () => this.translate.instant('api-key.delete-api-key-text');
this.deleteEntitiesTitle = count => this.translate.instant('api-key.delete-api-keys-title', {count});
this.deleteEntitiesContent = () => this.translate.instant('api-key.delete-api-keys-text');
this.deleteEntity = id => this.apiKeyService.deleteApiKey(id.id);
this.cellActionDescriptors = this.configureCellActions();
this.columns.push(
new DateEntityTableColumn<ApiKeyInfo>('createdTime', 'common.created-time', this.datePipe, '170px'),
new EntityTableColumn<ApiKeyInfo>('description', 'api-key.description', '100%',
(entity) => this.customTranslate.transform(entity?.description), () => ({}), true, () => ({}),
(entity) => entity?.description.length > 40 ? this.customTranslate.transform(entity.description) : undefined, false,
{
name: this.translate.instant('api-key.edit-description'),
icon: 'edit',
isEnabled: () => true,
onAction: ($event, entity) => this.updateApiKeyDescription($event, entity)
}),
new EntityTableColumn<ApiKeyInfo>('active', 'api-key.status', '80px',
entity => this.apiKeyStatus(entity), entity => this.apiKeyStatusStyle(entity), false),
new EntityTableColumn<ApiKeyInfo>('expirationTime', 'api-key.expiration-time', '120px',
(entity) => entity.expirationTime != 0 ?
this.datePipe.transform(entity.expirationTime, 'dd/MM/yyyy, HH:mm') :
this.translate.instant('api-key.expiration-time-never'),
),
);
}
private configureCellActions(): Array<CellActionDescriptor<ApiKeyInfo>> {
const actions: Array<CellActionDescriptor<ApiKeyInfo>> = [];
actions.push(
{
name: '',
nameFunction: (entity) => this.translate.instant(entity.enabled ? 'api-key.disable' : 'api-key.enable'),
icon: 'mdi:toggle-switch',
isEnabled: (entity) => !entity.expired,
iconFunction: (entity) => entity.enabled ? 'mdi:toggle-switch' : 'mdi:toggle-switch-off-outline',
onAction: ($event, entity) => this.toggleEnableMode($event, entity)
}
)
return actions;
}
private addApiKey(): Observable<ApiKey> {
return this.dialog.open<AddApiKeyDialogComponent, ApiKeysTableDialogData, ApiKey>(AddApiKeyDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
userId: this.userId
}
}).afterClosed().pipe(map(res => {
if (res) {
this.apiKeyGenerated(res);
} else {
return null;
}
}));
}
private apiKeyGenerated(apiKey: ApiKey) {
this.dialog.open<ApiKeyGeneratedDialogComponent, ApiKeyGeneratedDialogData>(ApiKeyGeneratedDialogComponent, {
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
apiKey
}
}).afterClosed()
.subscribe(() => {
this.updateData();
});
}
private toggleEnableMode($event: Event, entity: ApiKeyInfo): void {
if ($event) {
$event.stopPropagation();
}
this.apiKeyService.enableApiKey(entity.id.id, !entity.enabled, {ignoreLoading: true})
.subscribe(
() => this.updateData()
);
}
private apiKeyStatus(apiKey: ApiKeyInfo): string {
let translateKey = 'api-key.status-active';
let backgroundColor = 'rgba(25, 128, 56, 0.08)';
if (apiKey.expired) {
translateKey = 'api-key.status-expired';
backgroundColor = 'rgba(0, 0, 0, 0.04)';
} else if (!apiKey.enabled) {
translateKey = 'api-key.status-inactive';
backgroundColor = 'rgba(209, 39, 48, 0.08)';
}
return `<div class="status" style="border-radius: 16px; height: 32px;
line-height: 32px; padding: 0 12px; width: fit-content; background-color: ${backgroundColor}">
${this.translate.instant(translateKey)}
</div>`;
}
private apiKeyStatusStyle(apiKey: ApiKeyInfo): object {
const styleObj = {
fontSize: '14px',
color: '#198038',
cursor: 'pointer'
};
if (apiKey.expired) {
styleObj.color = 'rgba(0, 0, 0, 0.54)';
} else if (!apiKey.enabled) {
styleObj.color = '#d12730';
}
return styleObj;
}
private updateApiKeyDescription($event: Event, entity: ApiKeyInfo) {
if ($event) {
$event.stopPropagation();
}
const trigger = ($event.target || $event.srcElement || $event.currentTarget) as Element;
if (this.popoverService.hasPopover(trigger)) {
this.popoverService.hidePopover(trigger);
} else {
const editSecretDescriptionPanelPopover = this.popoverService.displayPopover({
trigger,
renderer: this.renderer,
componentType: EditApiKeyDescriptionPanelComponent,
hostView: this.viewContainerRef,
preferredPlacement: ['right', 'bottom', 'top'],
context: {
apiKeyId: entity.id.id,
description: entity.description
},
isModal: true
});
editSecretDescriptionPanelPopover.tbComponentRef.instance.descriptionApplied.subscribe(() => {
editSecretDescriptionPanelPopover.hide();
this.updateData();
});
}
}
}