28 changed files with 1301 additions and 2 deletions
@ -0,0 +1,54 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2025 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 { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils'; |
||||
|
import { Observable } from 'rxjs'; |
||||
|
import { PageLink } from '@shared/models/page/page-link'; |
||||
|
import { PageData } from '@shared/models/page/page-data'; |
||||
|
import { ApiKeyInfo, ApiKey } from '@shared/models/api-key.models'; |
||||
|
|
||||
|
@Injectable({ |
||||
|
providedIn: 'root' |
||||
|
}) |
||||
|
export class ApiKeyService { |
||||
|
|
||||
|
constructor( |
||||
|
private http: HttpClient |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public saveApiKey(apiKey: ApiKeyInfo, config?: RequestConfig): Observable<ApiKey> { |
||||
|
return this.http.post<ApiKey>('/api/apiKey', apiKey, defaultHttpOptionsFromConfig(config)); |
||||
|
} |
||||
|
|
||||
|
public deleteApiKey(id: string, config?: RequestConfig): Observable<void> { |
||||
|
return this.http.delete<void>(`/api/apiKey/${id}`, defaultHttpOptionsFromConfig(config)); |
||||
|
} |
||||
|
|
||||
|
public updateApiKeyDescription(id: string, description: string, config?: RequestConfig): Observable<ApiKeyInfo> { |
||||
|
return this.http.put<ApiKeyInfo>(`/api/apiKey/${id}/description`, description, defaultHttpOptionsFromConfig(config)); |
||||
|
} |
||||
|
|
||||
|
public enableApiKey(id: string, enabledValue: boolean, config?: RequestConfig): Observable<ApiKeyInfo> { |
||||
|
return this.http.put<ApiKeyInfo>(`/api/apiKey/${id}/enabled/${enabledValue}`, defaultHttpOptionsFromConfig(config)); |
||||
|
} |
||||
|
|
||||
|
public getUserApiKeys(userId: string, pageLink: PageLink, config?: RequestConfig): Observable<PageData<ApiKeyInfo>> { |
||||
|
return this.http.get<PageData<ApiKeyInfo>>(`/api/apiKeys/${userId}${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config)); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,207 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2025 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 |
||||
|
} 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/components/dialog/add-api-key-dialog.component'; |
||||
|
import { |
||||
|
EditApiKeyDescriptionPanelComponent |
||||
|
} from '@home/components/api-key/components/dialog/edit-api-key-description-panel.component'; |
||||
|
import { ApiKeysTableDialogData } from '@home/components/api-key/components/dialog/api-keys-table-dialog.component'; |
||||
|
import { |
||||
|
ApiKeyGeneratedDialogComponent, ApiKeyGeneratedDialogData |
||||
|
} from '@home/components/api-key/components/dialog/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.addAsTextButton = true; |
||||
|
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.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 = [{ |
||||
|
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) |
||||
|
}]; |
||||
|
|
||||
|
this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; |
||||
|
|
||||
|
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 > 80 ? 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 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(); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2025 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. |
||||
|
|
||||
|
--> |
||||
|
@if (apiKeysTableConfig) { |
||||
|
<tb-entities-table [entitiesTableConfig]="apiKeysTableConfig" [class.tb-details-mode]="true"></tb-entities-table> |
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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 '../../src/scss/constants.scss'; |
||||
|
@import '../../src/scss/mixins'; |
||||
|
|
||||
|
:host ::ng-deep { |
||||
|
tb-entities-table { |
||||
|
.mat-drawer-container { |
||||
|
background-color: white; |
||||
|
|
||||
|
mat-cell.mat-column-description { |
||||
|
white-space: nowrap; |
||||
|
.mat-mdc-icon-button { |
||||
|
vertical-align: middle; |
||||
|
margin-left: 8px; |
||||
|
@include tb-mat-icon-button-size(32); |
||||
|
.mat-icon { |
||||
|
@include tb-mat-icon-size(20); |
||||
|
} |
||||
|
} |
||||
|
span { |
||||
|
display: inline-block; |
||||
|
max-width: 40ch; |
||||
|
overflow: hidden; |
||||
|
text-overflow: ellipsis; |
||||
|
vertical-align: middle; |
||||
|
pointer-events: none; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,80 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2025 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 { |
||||
|
ChangeDetectionStrategy, |
||||
|
ChangeDetectorRef, |
||||
|
Component, |
||||
|
effect, |
||||
|
input, |
||||
|
Renderer2, |
||||
|
ViewChild, |
||||
|
ViewContainerRef, |
||||
|
} from '@angular/core'; |
||||
|
import { EntitiesTableComponent } from '@home/components/entity/entities-table.component'; |
||||
|
import { TranslateService } from '@ngx-translate/core'; |
||||
|
import { MatDialog } from '@angular/material/dialog'; |
||||
|
import { DatePipe } from '@angular/common'; |
||||
|
import { ApiKeysTableConfig } from '@home/components/api-key/api-keys-table-config'; |
||||
|
import { ApiKeyService } from '@core/http/api-key.service'; |
||||
|
import { CustomTranslatePipe } from '@shared/pipe/custom-translate.pipe'; |
||||
|
import { TbPopoverService } from '@shared/components/popover.service'; |
||||
|
import { UserId } from '@shared/models/id/user-id'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-api-keys-table', |
||||
|
templateUrl: './api-keys-table.component.html', |
||||
|
styleUrls: ['./api-keys-table.component.scss'], |
||||
|
changeDetection: ChangeDetectionStrategy.OnPush |
||||
|
}) |
||||
|
export class ApiKeysTableComponent { |
||||
|
|
||||
|
@ViewChild(EntitiesTableComponent, {static: true}) entitiesTable: EntitiesTableComponent; |
||||
|
|
||||
|
active = input<boolean>(); |
||||
|
userId = input<UserId>(); |
||||
|
|
||||
|
apiKeysTableConfig: ApiKeysTableConfig; |
||||
|
|
||||
|
constructor( |
||||
|
private apiKeyService: ApiKeyService, |
||||
|
private translate: TranslateService, |
||||
|
private customTranslate: CustomTranslatePipe, |
||||
|
private dialog: MatDialog, |
||||
|
private datePipe: DatePipe, |
||||
|
private cd: ChangeDetectorRef, |
||||
|
private popoverService: TbPopoverService, |
||||
|
private renderer: Renderer2, |
||||
|
private viewContainerRef: ViewContainerRef, |
||||
|
) { |
||||
|
effect(() => { |
||||
|
if (this.active()) { |
||||
|
this.apiKeysTableConfig = new ApiKeysTableConfig( |
||||
|
this.apiKeyService, |
||||
|
this.translate, |
||||
|
this.customTranslate, |
||||
|
this.dialog, |
||||
|
this.datePipe, |
||||
|
this.popoverService, |
||||
|
this.renderer, |
||||
|
this.viewContainerRef, |
||||
|
this.userId(), |
||||
|
); |
||||
|
this.cd.markForCheck(); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,84 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2025 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. |
||||
|
|
||||
|
--> |
||||
|
<form (ngSubmit)="add()"> |
||||
|
<mat-toolbar color="primary"> |
||||
|
<h2>{{ 'api-key.generate-title' | translate }}</h2> |
||||
|
<span class="flex-1"></span> |
||||
|
<div tb-help="apiKeys"></div> |
||||
|
<button mat-icon-button |
||||
|
(click)="close()" |
||||
|
type="button"> |
||||
|
<mat-icon class="material-icons">close</mat-icon> |
||||
|
</button> |
||||
|
</mat-toolbar> |
||||
|
@if (isLoading$ | async) { |
||||
|
<mat-progress-bar color="warn" mode="indeterminate"></mat-progress-bar> |
||||
|
} @else { |
||||
|
<div style="height: 4px;"></div> |
||||
|
} |
||||
|
<div mat-dialog-content> |
||||
|
<section class="tb-form-panel no-border no-padding" [formGroup]="apiKeyForm"> |
||||
|
<div class="api-key-text"> |
||||
|
<span translate>api-key.generate-text</span> |
||||
|
</div> |
||||
|
<mat-form-field class="mat-block" appearance="outline" subscriptSizing="dynamic"> |
||||
|
<mat-label translate>api-key.description</mat-label> |
||||
|
<textarea #input cdkTextareaAutosize matInput formControlName="description" rows="2" maxLength="255"></textarea> |
||||
|
</mat-form-field> |
||||
|
<mat-slide-toggle class="mat-slide" formControlName="enabled"> |
||||
|
{{ 'api-key.enable' | translate }} |
||||
|
</mat-slide-toggle> |
||||
|
<section class="flex gap-3"> |
||||
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="flex-1"> |
||||
|
<mat-select formControlName="expirationTime" aria-label="Expiration date selector" (selectionChange)="onExpirationDateChange()"> |
||||
|
<mat-option [value]="value" *ngFor="let value of expirationDates"> |
||||
|
{{ value | dateExpiration }} |
||||
|
</mat-option> |
||||
|
<mat-option value="never">{{'api-key.expiration-time-never' | translate}}</mat-option> |
||||
|
<mat-option value="custom">{{'api-key.expiration-time-custom' | translate}}</mat-option> |
||||
|
</mat-select> |
||||
|
</mat-form-field> |
||||
|
@if (isCustomExpirationTime()) { |
||||
|
<mat-form-field class="flex-1" appearance="outline" subscriptSizing="dynamic"> |
||||
|
<mat-label translate>api-key.date</mat-label> |
||||
|
<mat-datetimepicker-toggle [for]="datePicker" matSuffix></mat-datetimepicker-toggle> |
||||
|
<mat-datetimepicker #datePicker type="datetime" openOnFocus="true"></mat-datetimepicker> |
||||
|
<input matInput required formControlName="customExpirationTime" |
||||
|
[matDatetimepicker]="datePicker" |
||||
|
[min]="startDate"/> |
||||
|
</mat-form-field> |
||||
|
} |
||||
|
</section> |
||||
|
</section> |
||||
|
</div> |
||||
|
<div mat-dialog-actions class="flex items-center justify-end"> |
||||
|
<button mat-button color="primary" |
||||
|
type="button" |
||||
|
cdkFocusInitial |
||||
|
[disabled]="(isLoading$ | async)" |
||||
|
(click)="close()"> |
||||
|
{{ 'action.cancel' | translate }} |
||||
|
</button> |
||||
|
<button mat-raised-button color="primary" |
||||
|
type="submit" |
||||
|
[disabled]="(isLoading$ | async) || apiKeyForm?.invalid"> |
||||
|
{{ 'api-key.generate' | translate }} |
||||
|
</button> |
||||
|
</div> |
||||
|
</form> |
||||
|
|
||||
@ -0,0 +1,39 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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 '../../src/scss/constants'; |
||||
|
|
||||
|
:host{ |
||||
|
form { |
||||
|
width: 700px; |
||||
|
} |
||||
|
.api-key-text { |
||||
|
position: relative; |
||||
|
padding: 8px 16px 8px 16px; |
||||
|
&::before { |
||||
|
content: ''; |
||||
|
position: absolute; |
||||
|
inset: 0; |
||||
|
background-color: $tb-primary-color; |
||||
|
border-radius: 6px; |
||||
|
opacity: 0.04; |
||||
|
} |
||||
|
|
||||
|
span { |
||||
|
font-size: 12px; |
||||
|
color: rgba(0, 0, 0, 0.54); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,111 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2025 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, OnInit, Inject } from '@angular/core'; |
||||
|
import { DialogComponent } from '@shared/components/dialog.component'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { Router } from '@angular/router'; |
||||
|
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; |
||||
|
import { FormBuilder, Validators, UntypedFormGroup } from '@angular/forms'; |
||||
|
import { deepTrim } from '@core/utils'; |
||||
|
import { ApiKeyService } from '@core/http/api-key.service'; |
||||
|
import { ApiKeyInfo } from '@shared/models/api-key.models'; |
||||
|
import { ApiKeysTableDialogData } from '@home/components/api-key/components/dialog/api-keys-table-dialog.component'; |
||||
|
import { DAY } from '@shared/models/time/time.models'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-add-api-key-dialog', |
||||
|
templateUrl: './add-api-key-dialog.component.html', |
||||
|
styleUrls: ['./add-api-key-dialog.component.scss'] |
||||
|
}) |
||||
|
export class AddApiKeyDialogComponent extends DialogComponent<AddApiKeyDialogComponent, ApiKeyInfo | string> implements OnInit{ |
||||
|
|
||||
|
apiKeyForm: UntypedFormGroup; |
||||
|
|
||||
|
readonly startDate = new Date(); |
||||
|
readonly expirationDates = [7, 30, 60, 90].map(days => days * DAY); |
||||
|
|
||||
|
private defaultExpirationDate = this.expirationDates[1]; |
||||
|
|
||||
|
constructor( |
||||
|
protected store: Store<AppState>, |
||||
|
protected router: Router, |
||||
|
public dialogRef: MatDialogRef<AddApiKeyDialogComponent, ApiKeyInfo | string>, |
||||
|
private fb: FormBuilder, |
||||
|
private apiKeyService: ApiKeyService, |
||||
|
@Inject(MAT_DIALOG_DATA) public data: ApiKeysTableDialogData, |
||||
|
) { |
||||
|
super(store, router, dialogRef); |
||||
|
} |
||||
|
|
||||
|
ngOnInit() { |
||||
|
this.apiKeyForm = this.fb.group({ |
||||
|
description: [{value: null, disabled: false}, [Validators.required]], |
||||
|
enabled: [{value: true, disabled: false}, []], |
||||
|
expirationTime: [{value: this.defaultExpirationDate, disabled: false}, [Validators.required]], |
||||
|
customExpirationTime: [{value: null, disabled: true}, []], |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
close(): void { |
||||
|
this.dialogRef.close(null); |
||||
|
} |
||||
|
|
||||
|
add(): void { |
||||
|
const formValue = this.apiKeyForm.value; |
||||
|
const userId = this.data.userId; |
||||
|
const expirationTime = this.calcExpirationTime(); |
||||
|
const apiKey = { |
||||
|
...deepTrim(formValue), |
||||
|
expirationTime, |
||||
|
userId, |
||||
|
} as ApiKeyInfo; |
||||
|
this.apiKeyService.saveApiKey(apiKey).subscribe( |
||||
|
(res) => { |
||||
|
this.dialogRef.close(res); |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
isCustomExpirationTime() { |
||||
|
return this.apiKeyForm.value?.expirationTime === 'custom'; |
||||
|
} |
||||
|
|
||||
|
onExpirationDateChange() { |
||||
|
const customExpirationTimeControl = this.apiKeyForm.get('customExpirationTime'); |
||||
|
if (this.isCustomExpirationTime()) { |
||||
|
customExpirationTimeControl.enable(); |
||||
|
} else { |
||||
|
customExpirationTimeControl.disable(); |
||||
|
} |
||||
|
customExpirationTimeControl.updateValueAndValidity(); |
||||
|
} |
||||
|
|
||||
|
private calcExpirationTime(): number { |
||||
|
const expirationTimeValue = this.apiKeyForm.get('expirationTime').value; |
||||
|
let value: number; |
||||
|
if (this.isCustomExpirationTime()) { |
||||
|
value = this.apiKeyForm.get('customExpirationTime').value.getTime(); |
||||
|
} else if (expirationTimeValue === 'never') { |
||||
|
value = 0; |
||||
|
} else { |
||||
|
value = expirationTimeValue + Date.now(); |
||||
|
} |
||||
|
return value; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,46 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2025 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. |
||||
|
|
||||
|
--> |
||||
|
<mat-toolbar color="primary"> |
||||
|
<h2>{{ 'api-key.generated-title' | translate }}</h2> |
||||
|
<span class="flex-1"></span> |
||||
|
<button mat-icon-button |
||||
|
(click)="close()" |
||||
|
type="button"> |
||||
|
<mat-icon class="material-icons">close</mat-icon> |
||||
|
</button> |
||||
|
</mat-toolbar> |
||||
|
<div mat-dialog-content> |
||||
|
<span translate>api-key.generated-text</span> |
||||
|
<div class="tb-form-panel no-padding no-border tb-tab-body"> |
||||
|
<tb-markdown usePlainMarkdown containerClass="tb-command-code" |
||||
|
[data]='createMarkDownCommand(data.apiKey.value)'></tb-markdown> |
||||
|
<div class="tb-form-panel stroked"> |
||||
|
<div class="tb-form-panel-title" translate>api-key.generated-command-title</div> |
||||
|
<tb-markdown usePlainMarkdown containerClass="tb-command-code" |
||||
|
[data]='createMarkDownCommand(apiKeyCommand)'></tb-markdown> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div mat-dialog-actions class="justify-end"> |
||||
|
<button mat-raised-button color="primary" |
||||
|
type="button" |
||||
|
[disabled]="(isLoading$ | async)" |
||||
|
(click)="close()"> |
||||
|
{{ 'action.close' | translate }} |
||||
|
</button> |
||||
|
</div> |
||||
@ -0,0 +1,85 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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. |
||||
|
*/ |
||||
|
:host{ |
||||
|
display: block; |
||||
|
width: 600px; |
||||
|
max-width: 100%; |
||||
|
color: rgba(0, 0, 0, 0.76); |
||||
|
} |
||||
|
|
||||
|
:host ::ng-deep { |
||||
|
.tb-markdown-view { |
||||
|
.tb-command-code { |
||||
|
.code-wrapper { |
||||
|
padding: 0; |
||||
|
pre[class*=language-] { |
||||
|
margin: 0; |
||||
|
background: #F3F6FA; |
||||
|
border-color: #305680; |
||||
|
padding-right: 38px; |
||||
|
padding-bottom: 4px; |
||||
|
min-height: 42px; |
||||
|
scrollbar-width: thin; |
||||
|
|
||||
|
&::-webkit-scrollbar { |
||||
|
width: 4px; |
||||
|
height: 4px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
button.clipboard-btn { |
||||
|
right: -2px; |
||||
|
p { |
||||
|
color: #305680; |
||||
|
} |
||||
|
p, div { |
||||
|
background-color: #F3F6FA; |
||||
|
} |
||||
|
div { |
||||
|
img { |
||||
|
display: none; |
||||
|
} |
||||
|
&:after { |
||||
|
content: ""; |
||||
|
position: initial; |
||||
|
display: block; |
||||
|
width: 18px; |
||||
|
height: 18px; |
||||
|
background: #305680; |
||||
|
mask-image: url(/assets/copy-code-icon.svg); |
||||
|
-webkit-mask-image: url(/assets/copy-code-icon.svg); |
||||
|
mask-repeat: no-repeat; |
||||
|
-webkit-mask-repeat: no-repeat; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
.mdc-button__label > span { |
||||
|
.mat-icon { |
||||
|
vertical-align: text-bottom; |
||||
|
box-sizing: initial; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.tabs-icon { |
||||
|
margin-right: 8px; |
||||
|
} |
||||
|
|
||||
|
.tb-form-panel.tb-tab-body { |
||||
|
padding: 16px 0 0; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2025 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 } from '@angular/core'; |
||||
|
import { DialogComponent } from '@shared/components/dialog.component'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { Router } from '@angular/router'; |
||||
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; |
||||
|
import { userInfoCommand, ApiKey } from '@shared/models/api-key.models'; |
||||
|
|
||||
|
export interface ApiKeyGeneratedDialogData { |
||||
|
apiKey: ApiKey; |
||||
|
} |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-api-key-generated-dialog', |
||||
|
templateUrl: './api-key-generated-dialog.component.html', |
||||
|
styleUrls: ['api-key-generated-dialog.component.scss'] |
||||
|
}) |
||||
|
export class ApiKeyGeneratedDialogComponent extends DialogComponent<ApiKeyGeneratedDialogComponent, void> { |
||||
|
|
||||
|
apiKeyCommand = userInfoCommand(this.data.apiKey.value); |
||||
|
|
||||
|
constructor(protected store: Store<AppState>, |
||||
|
protected router: Router, |
||||
|
protected dialogRef: MatDialogRef<ApiKeyGeneratedDialogComponent, void>, |
||||
|
@Inject(MAT_DIALOG_DATA) public data: ApiKeyGeneratedDialogData) { |
||||
|
super(store, router, dialogRef); |
||||
|
} |
||||
|
|
||||
|
close(): void { |
||||
|
this.dialogRef.close(null); |
||||
|
} |
||||
|
|
||||
|
createMarkDownCommand(command: string): string { |
||||
|
return '```bash\n' + |
||||
|
command + |
||||
|
'{:copy-code}\n' + |
||||
|
'```'; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2025 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. |
||||
|
|
||||
|
--> |
||||
|
<mat-toolbar color="primary"> |
||||
|
<h2>{{ 'api-key.manage-api-keys' | translate }}</h2> |
||||
|
<span class="flex-1"></span> |
||||
|
<div tb-help="apiKeys"></div> |
||||
|
<button mat-icon-button |
||||
|
(click)="close()" |
||||
|
type="button"> |
||||
|
<mat-icon class="material-icons">close</mat-icon> |
||||
|
</button> |
||||
|
</mat-toolbar> |
||||
|
<tb-api-keys-table [active]="true" [userId]="data.userId"/> |
||||
|
<div mat-dialog-actions class="justify-end"> |
||||
|
<button mat-raised-button color="primary" |
||||
|
type="button" |
||||
|
(click)="close()"> |
||||
|
{{ 'action.close' | translate }} |
||||
|
</button> |
||||
|
</div> |
||||
@ -0,0 +1,36 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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. |
||||
|
*/ |
||||
|
|
||||
|
:host ::ng-deep { |
||||
|
tb-api-keys-table { |
||||
|
tb-entities-table { |
||||
|
.tb-absolute-fill { |
||||
|
position: relative; |
||||
|
} |
||||
|
.table-container { |
||||
|
width: 1000px; |
||||
|
min-height: 625px; |
||||
|
max-height: 625px; |
||||
|
} |
||||
|
.no-data-found { |
||||
|
position: absolute; |
||||
|
top: 50%; |
||||
|
left: 50%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2025 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 } from '@angular/core'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { Router } from '@angular/router'; |
||||
|
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; |
||||
|
import { UserId } from '@shared/models/id/user-id'; |
||||
|
|
||||
|
export interface ApiKeysTableDialogData { |
||||
|
userId: UserId; |
||||
|
} |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-api-keys-table-dialog', |
||||
|
templateUrl: './api-keys-table-dialog.component.html', |
||||
|
styleUrls: ['api-keys-table-dialog.component.scss'] |
||||
|
}) |
||||
|
export class ApiKeysTableDialogComponent { |
||||
|
|
||||
|
constructor( |
||||
|
protected store: Store<AppState>, |
||||
|
protected router: Router, |
||||
|
public dialogRef: MatDialogRef<ApiKeysTableDialogComponent>, |
||||
|
@Inject(MAT_DIALOG_DATA) public data: ApiKeysTableDialogData, |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
close(): void { |
||||
|
this.dialogRef.close(null); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2025 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. |
||||
|
|
||||
|
--> |
||||
|
|
||||
|
<div class="tb-edit-api-key-description-panel"> |
||||
|
<div class="tb-edit-api-key-description-title">{{ 'api-key.edit-description' | translate }}</div> |
||||
|
<mat-form-field class="mat-block" appearance="outline"> |
||||
|
<mat-label translate>api-key.description</mat-label> |
||||
|
<textarea #input cdkTextareaAutosize matInput [formControl]="descriptionFormControl" rows="2" maxLength="255"></textarea> |
||||
|
<mat-hint align="end">{{input.value?.length || 0}}/255</mat-hint> |
||||
|
</mat-form-field> |
||||
|
<div class="tb-edit-api-key-description-panel-buttons"> |
||||
|
<button mat-button |
||||
|
color="primary" |
||||
|
type="button" |
||||
|
(click)="cancel()"> |
||||
|
{{ 'action.cancel' | translate }} |
||||
|
</button> |
||||
|
<button mat-raised-button |
||||
|
color="primary" |
||||
|
type="button" |
||||
|
(click)="applyDescription()" |
||||
|
[disabled]="descriptionFormControl.invalid || !descriptionFormControl.dirty"> |
||||
|
{{ 'action.save' | translate }} |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
@ -0,0 +1,41 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2025 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. |
||||
|
*/ |
||||
|
|
||||
|
|
||||
|
.tb-edit-api-key-description-panel { |
||||
|
--mdc-outlined-text-field-outline-color: rgba(0,0,0,0.12); |
||||
|
|
||||
|
width: 400px; |
||||
|
max-width: 90vw; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 16px; |
||||
|
.tb-edit-api-key-description-title { |
||||
|
font-size: 16px; |
||||
|
font-weight: 500; |
||||
|
line-height: 24px; |
||||
|
letter-spacing: 0.25px; |
||||
|
color: rgba(0, 0, 0, 0.87); |
||||
|
} |
||||
|
.tb-edit-api-key-description-panel-buttons { |
||||
|
height: 40px; |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
gap: 16px; |
||||
|
justify-content: flex-end; |
||||
|
align-items: flex-end; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,61 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2025 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, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core'; |
||||
|
import { FormBuilder } from '@angular/forms'; |
||||
|
import { TbPopoverComponent } from '@shared/components/popover.component'; |
||||
|
import { ApiKeyService } from '@core/http/api-key.service'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-edit-api-key-description-panel', |
||||
|
templateUrl: './edit-api-key-description-panel.component.html', |
||||
|
styleUrls: ['./edit-api-key-description-panel.component.scss'], |
||||
|
encapsulation: ViewEncapsulation.None |
||||
|
}) |
||||
|
export class EditApiKeyDescriptionPanelComponent implements OnInit { |
||||
|
|
||||
|
@Input() |
||||
|
apiKeyId: string; |
||||
|
|
||||
|
@Input() |
||||
|
description: string; |
||||
|
|
||||
|
@Output() |
||||
|
descriptionApplied = new EventEmitter<string>(); |
||||
|
|
||||
|
descriptionFormControl = this.fb.control<string>(null); |
||||
|
|
||||
|
constructor(private fb: FormBuilder, |
||||
|
private popover: TbPopoverComponent<EditApiKeyDescriptionPanelComponent>, |
||||
|
private apiKeyService: ApiKeyService) {} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.descriptionFormControl.setValue(this.description, {emitEvent: false}); |
||||
|
} |
||||
|
|
||||
|
cancel() { |
||||
|
this.popover.hide(); |
||||
|
} |
||||
|
|
||||
|
applyDescription() { |
||||
|
const description = this.descriptionFormControl.value.trim(); |
||||
|
this.apiKeyService.updateApiKeyDescription(this.apiKeyId, description).subscribe(() => { |
||||
|
this.descriptionApplied.emit(description); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2025 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 { BaseData } from '@shared/models/base-data'; |
||||
|
import { HasTenantId } from '@shared/models/entity.models'; |
||||
|
import { ApiKeyId } from '@shared/models/id/api-key-id'; |
||||
|
import { UserId } from '@shared/models/id/user-id'; |
||||
|
|
||||
|
export const userInfoCommand = (key: string): string => `curl -X GET "${window.location.origin}/api/auth/user" -H "Content-Type: application/json" -H "X-Authorization: ApiKey ${key}"` |
||||
|
|
||||
|
export interface ApiKeyInfo extends BaseData<ApiKeyId>, HasTenantId { |
||||
|
enabled: boolean; |
||||
|
expirationTime: number; |
||||
|
description: string; |
||||
|
expired: boolean; |
||||
|
userId: UserId; |
||||
|
} |
||||
|
|
||||
|
export interface ApiKey extends ApiKeyInfo { |
||||
|
value: string; |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2025 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 { EntityId } from './entity-id'; |
||||
|
import { EntityType } from '@shared/models/entity-type.models'; |
||||
|
|
||||
|
export class ApiKeyId implements EntityId { |
||||
|
entityType = EntityType.API_KEY; |
||||
|
id: string; |
||||
|
constructor(id: string) { |
||||
|
this.id = id; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2025 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 { Pipe, PipeTransform } from '@angular/core'; |
||||
|
import { DatePipe } from '@angular/common'; |
||||
|
import { MillisecondsToTimeStringPipe } from '@shared/pipe/milliseconds-to-time-string.pipe'; |
||||
|
import { isDefined } from '@core/utils'; |
||||
|
|
||||
|
@Pipe({ |
||||
|
name: 'dateExpiration' |
||||
|
}) |
||||
|
export class DateExpirationPipe implements PipeTransform { |
||||
|
|
||||
|
constructor(private millisecondsToTimeString: MillisecondsToTimeStringPipe, private datePipe: DatePipe) { |
||||
|
} |
||||
|
|
||||
|
transform(expirationMs: number, arg?: any): string { |
||||
|
const displayDate = isDefined(arg?.displayDate) ? arg.displayDate : true; |
||||
|
const dateFormat = isDefined(arg?.dateFormat) ? arg.dateFormat : ' (dd/MM/yyyy)'; |
||||
|
const shortFormat = isDefined(arg?.shortFormat) ? arg.shortFormat : true; |
||||
|
const onlyFirstDigit = isDefined(arg?.onlyFirstDigit) ? arg.onlyFirstDigit : true; |
||||
|
let time = this.millisecondsToTimeString.transform(expirationMs, shortFormat, onlyFirstDigit); |
||||
|
if (displayDate) { |
||||
|
const exactDate = this.datePipe.transform(expirationMs + Date.now(), dateFormat); |
||||
|
time += exactDate; |
||||
|
} |
||||
|
return time; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue