31 changed files with 1008 additions and 421 deletions
@ -1,143 +0,0 @@ |
|||
///
|
|||
/// Copyright © 2016-2024 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 { ActivatedRouteSnapshot } from '@angular/router'; |
|||
import { |
|||
CellActionDescriptorType, |
|||
DateEntityTableColumn, |
|||
EntityActionTableColumn, |
|||
EntityChipsEntityTableColumn, |
|||
EntityTableColumn, |
|||
EntityTableConfig |
|||
} from '@home/models/entity/entities-table-config.models'; |
|||
import { MobileApp, MobileAppInfo } from '@shared/models/oauth2.models'; |
|||
import { TranslateService } from '@ngx-translate/core'; |
|||
import { DatePipe } from '@angular/common'; |
|||
import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models'; |
|||
import { isEqual } from '@core/utils'; |
|||
import { Direction } from '@app/shared/models/page/sort-order'; |
|||
import { MobileAppService } from '@core/http/mobile-app.service'; |
|||
import { MobileAppComponent } from '@home/pages/admin/oauth2/mobile-apps/mobile-app.component'; |
|||
import { MobileAppTableHeaderComponent } from '@home/pages/admin/oauth2/mobile-apps/mobile-app-table-header.component'; |
|||
import { map, Observable, of, mergeMap } from 'rxjs'; |
|||
|
|||
@Injectable() |
|||
export class MobileAppTableConfigResolver { |
|||
|
|||
private readonly config: EntityTableConfig<MobileAppInfo> = new EntityTableConfig<MobileAppInfo>(); |
|||
|
|||
constructor(private translate: TranslateService, |
|||
private datePipe: DatePipe, |
|||
private mobileAppService: MobileAppService) { |
|||
this.config.tableTitle = this.translate.instant('admin.oauth2.mobile-apps'); |
|||
this.config.selectionEnabled = false; |
|||
this.config.entityType = EntityType.MOBILE_APP; |
|||
this.config.rowPointer = true; |
|||
this.config.entityTranslations = entityTypeTranslations.get(EntityType.MOBILE_APP); |
|||
this.config.entityResources = entityTypeResources.get(EntityType.MOBILE_APP); |
|||
this.config.entityComponent = MobileAppComponent; |
|||
this.config.headerComponent = MobileAppTableHeaderComponent; |
|||
this.config.addDialogStyle = {width: '850px', maxHeight: '100vh'}; |
|||
this.config.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; |
|||
|
|||
this.config.columns.push( |
|||
new DateEntityTableColumn<MobileAppInfo>('createdTime', 'common.created-time', this.datePipe, '170px'), |
|||
new EntityTableColumn<MobileAppInfo>('pkgName', 'admin.oauth2.mobile-package', '170px'), |
|||
new EntityTableColumn<MobileAppInfo>('appSecret', 'admin.oauth2.mobile-app-secret', '350px', |
|||
(entity) => entity.appSecret ? this.appSecretText(entity) : '', () => ({}), |
|||
false, () => ({}), () => undefined, false, |
|||
{ |
|||
name: this.translate.instant('admin.oauth2.copy-mobile-app-secret'), |
|||
icon: 'content_copy', |
|||
style: { |
|||
padding: '4px', |
|||
'font-size': '16px', |
|||
color: 'rgba(0,0,0,.87)' |
|||
}, |
|||
isEnabled: (entity) => !!entity.appSecret, |
|||
onAction: ($event, entity) => entity.appSecret, |
|||
type: CellActionDescriptorType.COPY_BUTTON |
|||
}), |
|||
new EntityChipsEntityTableColumn<MobileAppInfo>('oauth2ClientInfos', 'admin.oauth2.clients', '20%'), |
|||
new EntityActionTableColumn('oauth2Enabled', 'admin.oauth2.enable', |
|||
{ |
|||
name: '', |
|||
nameFunction: (app) => |
|||
this.translate.instant(app.oauth2Enabled ? 'admin.oauth2.disable' : 'admin.oauth2.enable'), |
|||
icon: 'mdi:toggle-switch', |
|||
iconFunction: (entity) => entity.oauth2Enabled ? 'mdi:toggle-switch' : 'mdi:toggle-switch-off-outline', |
|||
isEnabled: () => true, |
|||
onAction: ($event, entity) => this.toggleEnableOAuth($event, entity) |
|||
}) |
|||
); |
|||
|
|||
this.config.deleteEntityTitle = (app) => this.translate.instant('admin.oauth2.delete-mobile-app-title', {applicationName: app.pkgName}); |
|||
this.config.deleteEntityContent = () => this.translate.instant('admin.oauth2.delete-mobile-app-text'); |
|||
this.config.entitiesFetchFunction = pageLink => this.mobileAppService.getTenantMobileAppInfos(pageLink); |
|||
this.config.loadEntity = id => this.mobileAppService.getMobileAppInfoById(id.id); |
|||
this.config.saveEntity = (mobileApp, originalMobileApp) => { |
|||
const clientsIds = mobileApp.oauth2ClientInfos as Array<string> || []; |
|||
let clientsTask: Observable<void>; |
|||
if (mobileApp.id && !isEqual(mobileApp.oauth2ClientInfos?.sort(), |
|||
originalMobileApp.oauth2ClientInfos?.map(info => info.id ? info.id.id : info).sort())) { |
|||
clientsTask = this.mobileAppService.updateOauth2Clients(mobileApp.id.id, clientsIds); |
|||
} else { |
|||
clientsTask = of(null); |
|||
} |
|||
delete mobileApp.oauth2ClientInfos; |
|||
return clientsTask.pipe( |
|||
mergeMap(() => this.mobileAppService.saveMobileApp(mobileApp as MobileApp, mobileApp.id ? [] : clientsIds)), |
|||
map(savedMobileApp => { |
|||
(savedMobileApp as MobileAppInfo).oauth2ClientInfos = clientsIds; |
|||
return savedMobileApp; |
|||
}) |
|||
); |
|||
}; |
|||
this.config.deleteEntity = id => this.mobileAppService.deleteMobileApp(id.id); |
|||
} |
|||
|
|||
resolve(route: ActivatedRouteSnapshot): EntityTableConfig<MobileAppInfo> { |
|||
return this.config; |
|||
} |
|||
|
|||
private toggleEnableOAuth($event: Event, mobileApp: MobileAppInfo): void { |
|||
if ($event) { |
|||
$event.stopPropagation(); |
|||
} |
|||
|
|||
const modifiedMobileApp: MobileAppInfo = { |
|||
...mobileApp, |
|||
oauth2Enabled: !mobileApp.oauth2Enabled |
|||
}; |
|||
|
|||
this.mobileAppService.saveMobileApp(modifiedMobileApp, mobileApp.oauth2ClientInfos.map(clientInfo => clientInfo.id.id), |
|||
{ignoreLoading: true}) |
|||
.subscribe((result) => { |
|||
mobileApp.oauth2Enabled = result.oauth2Enabled; |
|||
this.config.getTable().detectChanges(); |
|||
}); |
|||
} |
|||
|
|||
private appSecretText(entity): string { |
|||
let text = entity.appSecret; |
|||
if (text.length > 35) { |
|||
text = `${text.slice(0, 35)}…`; |
|||
} |
|||
return text; |
|||
} |
|||
|
|||
} |
|||
@ -1,76 +0,0 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2024 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. |
|||
|
|||
--> |
|||
<section class="tb-form-panel no-border" [class.no-padding]="isAdd" [formGroup]="entityForm"> |
|||
<div class="tb-form-row tb-standard-fields no-border no-padding"> |
|||
<mat-form-field class="flex" floatLabel="always" subscriptSizing="dynamic"> |
|||
<mat-label translate>admin.oauth2.mobile-package</mat-label> |
|||
<input matInput formControlName="pkgName" |
|||
placeholder="{{ 'admin.oauth2.mobile-package-placeholder' | translate }}" required> |
|||
<mat-hint translate>admin.oauth2.mobile-package-hint</mat-hint> |
|||
<mat-error *ngIf="entityForm.hasError('unique')"> |
|||
{{ 'admin.oauth2.mobile-package-unique' | translate }} |
|||
</mat-error> |
|||
<mat-error *ngIf="entityForm.get('pkgName').hasError('maxlength')"> |
|||
{{ 'admin.oauth2.mobile-package-max-length' | translate }} |
|||
</mat-error> |
|||
<mat-error *ngIf="entityForm.get('pkgName').hasError('pattern')"> |
|||
{{ 'admin.oauth2.mobile-package-spaces' | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
</div> |
|||
<div class="tb-form-row tb-standard-fields no-border no-padding"> |
|||
<mat-form-field class="flex" floatLabel="always" subscriptSizing="dynamic"> |
|||
<mat-label translate>admin.oauth2.mobile-app-secret</mat-label> |
|||
<input matInput formControlName="appSecret" required> |
|||
<tb-copy-button |
|||
matSuffix |
|||
miniButton="false" |
|||
color="primary" |
|||
[copyText]="entityForm.get('appSecret').value" |
|||
tooltipText="{{ 'admin.oauth2.copy-mobile-app-secret' | translate }}" |
|||
tooltipPosition="above" |
|||
icon="content_copy"> |
|||
</tb-copy-button> |
|||
<mat-hint translate>admin.oauth2.mobile-app-secret-hint</mat-hint> |
|||
<mat-error *ngIf="entityForm.get('appSecret').hasError('required')"> |
|||
{{ 'admin.oauth2.mobile-app-secret-required' | translate }} |
|||
</mat-error> |
|||
<mat-error *ngIf="entityForm.get('appSecret').hasError('base64')"> |
|||
{{ 'admin.oauth2.mobile-app-secret-min-length' | translate }} |
|||
</mat-error> |
|||
<mat-error *ngIf="entityForm.get('appSecret').hasError('minLength')"> |
|||
{{ 'admin.oauth2.mobile-app-secret-base64' | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
</div> |
|||
<div class="tb-form-row no-border no-padding"> |
|||
<mat-slide-toggle class="mat-slide" formControlName="oauth2Enabled"> |
|||
{{ 'admin.oauth2.enable' | translate }} |
|||
</mat-slide-toggle> |
|||
</div> |
|||
<tb-entity-list [entityType]="entityType.OAUTH2_CLIENT" formControlName="oauth2ClientInfos" |
|||
labelText="{{ 'admin.oauth2.clients' | translate }}" |
|||
placeholderText="{{ 'admin.oauth2.add-client' | translate }}"> |
|||
<button mat-button color="primary" matSuffix |
|||
[disabled]="!isEdit" |
|||
(click)="createClient($event)"> |
|||
<span style="white-space: nowrap">{{ 'admin.oauth2.create-new' | translate }}</span> |
|||
</button> |
|||
</tb-entity-list> |
|||
</section> |
|||
|
|||
@ -1,106 +0,0 @@ |
|||
///
|
|||
/// Copyright © 2016-2024 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 { ChangeDetectorRef, Component, Inject } from '@angular/core'; |
|||
import { EntityComponent } from '@home/components/entity/entity.component'; |
|||
import { MobileAppInfo } from '@shared/models/oauth2.models'; |
|||
import { AppState } from '@core/core.state'; |
|||
import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; |
|||
import { TranslateService } from '@ngx-translate/core'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms'; |
|||
import { isDefinedAndNotNull, randomAlphanumeric } from '@core/utils'; |
|||
import { MatDialog } from '@angular/material/dialog'; |
|||
import { ClientDialogComponent } from '@home/pages/admin/oauth2/clients/client-dialog.component'; |
|||
import { EntityType } from '@shared/models/entity-type.models'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-mobile-app', |
|||
templateUrl: './mobile-app.component.html', |
|||
styleUrls: [] |
|||
}) |
|||
export class MobileAppComponent extends EntityComponent<MobileAppInfo> { |
|||
|
|||
entityType = EntityType; |
|||
|
|||
constructor(protected store: Store<AppState>, |
|||
protected translate: TranslateService, |
|||
@Inject('entity') protected entityValue: MobileAppInfo, |
|||
@Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig<MobileAppInfo>, |
|||
protected cd: ChangeDetectorRef, |
|||
public fb: UntypedFormBuilder, |
|||
private dialog: MatDialog) { |
|||
super(store, fb, entityValue, entitiesTableConfigValue, cd); |
|||
} |
|||
|
|||
buildForm(entity: MobileAppInfo): UntypedFormGroup { |
|||
return this.fb.group({ |
|||
pkgName: [entity?.pkgName ? entity.pkgName : '', [Validators.required, Validators.maxLength(255), |
|||
Validators.pattern(/^\S+$/)]], |
|||
appSecret: [entity?.appSecret ? entity.appSecret : btoa(randomAlphanumeric(64)), |
|||
[Validators.required, this.base64Format]], |
|||
oauth2Enabled: isDefinedAndNotNull(entity?.oauth2Enabled) ? entity.oauth2Enabled : true, |
|||
oauth2ClientInfos: entity?.oauth2ClientInfos ? entity.oauth2ClientInfos.map(info => info.id.id) : [] |
|||
}); |
|||
} |
|||
|
|||
updateForm(entity: MobileAppInfo) { |
|||
this.entityForm.patchValue({ |
|||
pkgName: entity.pkgName, |
|||
appSecret: entity.appSecret, |
|||
oauth2Enabled: entity.oauth2Enabled, |
|||
oauth2ClientInfos: entity.oauth2ClientInfos?.map(info => info.id ? info.id.id : info) |
|||
}); |
|||
} |
|||
|
|||
createClient($event: Event) { |
|||
if ($event) { |
|||
$event.stopPropagation(); |
|||
$event.preventDefault(); |
|||
} |
|||
this.dialog.open<ClientDialogComponent>(ClientDialogComponent, { |
|||
disableClose: true, |
|||
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], |
|||
data: {} |
|||
}).afterClosed() |
|||
.subscribe((client) => { |
|||
if (client) { |
|||
const formValue = this.entityForm.get('oauth2ClientInfos').value ? |
|||
[...this.entityForm.get('oauth2ClientInfos').value] : []; |
|||
formValue.push(client.id.id); |
|||
this.entityForm.get('oauth2ClientInfos').patchValue(formValue); |
|||
this.entityForm.get('oauth2ClientInfos').markAsDirty(); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private base64Format(control: UntypedFormControl): { [key: string]: boolean } | null { |
|||
if (control.value === '') { |
|||
return null; |
|||
} |
|||
try { |
|||
const value = atob(control.value); |
|||
if (value.length < 64) { |
|||
return {minLength: true}; |
|||
} |
|||
return null; |
|||
} catch (e) { |
|||
return {base64: true}; |
|||
} |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,84 @@ |
|||
///
|
|||
/// Copyright © 2016-2024 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 { RouterModule, Routes } from '@angular/router'; |
|||
import { Authority } from '@shared/models/authority.enum'; |
|||
import { MenuId } from '@core/services/menu.models'; |
|||
import { EntitiesTableComponent } from '@home/components/entity/entities-table.component'; |
|||
import { MobileAppTableConfigResolver } from '@home/pages/mobile/applications/mobile-app-table-config.resolver'; |
|||
import { DevicesTableConfigResolver } from '@home/pages/device/devices-table-config.resolver'; |
|||
import { EntityDetailsPageComponent } from '@home/components/entity/entity-details-page.component'; |
|||
import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; |
|||
import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models'; |
|||
import { BreadCrumbConfig } from '@shared/components/breadcrumb'; |
|||
|
|||
export const applicationsRoutes: Routes = [ |
|||
{ |
|||
path: 'applications', |
|||
data: { |
|||
breadcrumb: { |
|||
menuId: MenuId.mobile_apps |
|||
} |
|||
}, |
|||
children: [ |
|||
{ |
|||
path: '', |
|||
component: EntitiesTableComponent, |
|||
data: { |
|||
auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN], |
|||
title: 'mobile.applications', |
|||
}, |
|||
resolve: { |
|||
entitiesTableConfig: MobileAppTableConfigResolver |
|||
} |
|||
}, |
|||
{ |
|||
path: ':entityId', |
|||
component: EntityDetailsPageComponent, |
|||
canDeactivate: [ConfirmOnExitGuard], |
|||
data: { |
|||
breadcrumb: { |
|||
labelFunction: entityDetailsPageBreadcrumbLabelFunction, |
|||
icon: 'list' |
|||
} as BreadCrumbConfig<EntityDetailsPageComponent>, |
|||
auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN], |
|||
title: 'mobile.applications', |
|||
}, |
|||
resolve: { |
|||
entitiesTableConfig: MobileAppTableConfigResolver |
|||
} |
|||
} |
|||
] |
|||
} |
|||
]; |
|||
|
|||
const routes: Routes = [ |
|||
{ |
|||
path: 'security-settings/oauth2/mobile-applications', |
|||
pathMatch: 'full', |
|||
redirectTo: '/mobile-center/applications' |
|||
} |
|||
]; |
|||
|
|||
@NgModule({ |
|||
providers: [ |
|||
MobileAppTableConfigResolver |
|||
], |
|||
imports: [RouterModule.forChild(routes)], |
|||
exports: [RouterModule] |
|||
}) |
|||
export class ApplicationsRoutingModule { } |
|||
@ -0,0 +1,37 @@ |
|||
///
|
|||
/// Copyright © 2016-2024 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 { MobileAppComponent } from '@home/pages/mobile/applications/mobile-app.component'; |
|||
import { MobileAppTableHeaderComponent } from '@home/pages/mobile/applications/mobile-app-table-header.component'; |
|||
import { CommonModule } from '@angular/common'; |
|||
import { SharedModule } from '@shared/shared.module'; |
|||
import { HomeComponentsModule } from '@home/components/home-components.module'; |
|||
import { ApplicationsRoutingModule } from '@home/pages/mobile/applications/applications-routing.module'; |
|||
|
|||
@NgModule({ |
|||
declarations: [ |
|||
MobileAppComponent, |
|||
MobileAppTableHeaderComponent |
|||
], |
|||
imports: [ |
|||
CommonModule, |
|||
SharedModule, |
|||
HomeComponentsModule, |
|||
ApplicationsRoutingModule |
|||
] |
|||
}) |
|||
export class ApplicationModule { } |
|||
@ -0,0 +1,151 @@ |
|||
///
|
|||
/// Copyright © 2016-2024 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 { ActivatedRouteSnapshot } from '@angular/router'; |
|||
import { |
|||
CellActionDescriptorType, |
|||
DateEntityTableColumn, |
|||
EntityTableColumn, |
|||
EntityTableConfig |
|||
} from '@home/models/entity/entities-table-config.models'; |
|||
import { TranslateService } from '@ngx-translate/core'; |
|||
import { DatePipe } from '@angular/common'; |
|||
import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models'; |
|||
import { Direction } from '@app/shared/models/page/sort-order'; |
|||
import { MobileAppService } from '@core/http/mobile-app.service'; |
|||
import { MobileAppComponent } from '@home/pages/mobile/applications/mobile-app.component'; |
|||
import { MobileAppTableHeaderComponent } from '@home/pages/mobile/applications/mobile-app-table-header.component'; |
|||
import { MobileApp, MobileAppStatus, mobileAppStatusTranslations } from '@shared/models/mobile-app.models'; |
|||
import { platformTypeTranslations } from '@shared/models/oauth2.models'; |
|||
import { TruncatePipe } from '@shared/pipe/truncate.pipe'; |
|||
|
|||
@Injectable() |
|||
export class MobileAppTableConfigResolver { |
|||
|
|||
private readonly config: EntityTableConfig<MobileApp> = new EntityTableConfig<MobileApp>(); |
|||
|
|||
constructor(private translate: TranslateService, |
|||
private datePipe: DatePipe, |
|||
private mobileAppService: MobileAppService, |
|||
private truncatePipe: TruncatePipe) { |
|||
this.config.selectionEnabled = false; |
|||
this.config.entityType = EntityType.MOBILE_APP; |
|||
this.config.addEnabled = false; |
|||
this.config.rowPointer = true; |
|||
this.config.entityTranslations = entityTypeTranslations.get(EntityType.MOBILE_APP); |
|||
this.config.entityResources = entityTypeResources.get(EntityType.MOBILE_APP); |
|||
this.config.entityComponent = MobileAppComponent; |
|||
this.config.headerComponent = MobileAppTableHeaderComponent; |
|||
this.config.addDialogStyle = {width: '850px', maxHeight: '100vh'}; |
|||
this.config.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; |
|||
|
|||
this.config.columns.push( |
|||
new DateEntityTableColumn<MobileApp>('createdTime', 'common.created-time', this.datePipe, '170px'), |
|||
new EntityTableColumn<MobileApp>('pkgName', 'mobile.application-package', '20%', (entity) => entity.pkgName ?? '', () => ({}), |
|||
false, () => ({}), () => undefined, false, |
|||
{ |
|||
name: this.translate.instant('mobile.copy-application-package'), |
|||
icon: 'content_copy', |
|||
style: { |
|||
padding: '4px', |
|||
'font-size': '16px', |
|||
color: 'rgba(0,0,0,.54)' |
|||
}, |
|||
isEnabled: (entity) => !!entity.pkgName, |
|||
onAction: (_$event, entity) => entity.pkgName, |
|||
type: CellActionDescriptorType.COPY_BUTTON |
|||
}), |
|||
new EntityTableColumn<MobileApp>('appSecret', 'mobile.application-secret', '15%', |
|||
(entity) => this.truncatePipe.transform(entity.appSecret, true, 10, '…'), () => ({}), |
|||
false, () => ({}), () => undefined, false, |
|||
{ |
|||
name: this.translate.instant('mobile.copy-application-secret'), |
|||
icon: 'content_copy', |
|||
style: { |
|||
padding: '4px', |
|||
'font-size': '16px', |
|||
color: 'rgba(0,0,0,.54)' |
|||
}, |
|||
isEnabled: (entity) => !!entity.appSecret, |
|||
onAction: (_$event, entity) => entity.appSecret, |
|||
type: CellActionDescriptorType.COPY_BUTTON |
|||
}), |
|||
new EntityTableColumn<MobileApp>('platformType', 'mobile.platform-type', '15%', |
|||
(entity) => this.translate.instant(platformTypeTranslations.get(entity.platformType)) |
|||
), |
|||
new EntityTableColumn<MobileApp>('status', 'mobile.status', '15%', |
|||
(entity) => `<span style="display: flex;">${this.mobileStatus(entity.status)}</span>`, |
|||
(entity)=> this.mobileStatusStyle(entity.status) |
|||
), |
|||
new EntityTableColumn<MobileApp>('minVersion', 'mobile.min-version', '15%', |
|||
(entity) => entity.versionInfo?.minVersion ?? '', () => ({}), false), |
|||
new EntityTableColumn<MobileApp>('latestVersion', 'mobile.latest-version', '15%', |
|||
(entity) => entity.versionInfo?.latestVersion ?? '', () => ({}), false), |
|||
); |
|||
|
|||
this.config.deleteEntityTitle = (app) => this.translate.instant('mobile.delete-applications-title', {applicationName: app.pkgName}); |
|||
this.config.deleteEntityContent = () => this.translate.instant('mobile.delete-applications-text'); |
|||
this.config.entitiesFetchFunction = pageLink => this.mobileAppService.getTenantMobileAppInfos(pageLink); |
|||
this.config.loadEntity = id => this.mobileAppService.getMobileAppInfoById(id.id); |
|||
this.config.saveEntity = (mobileApp) => this.mobileAppService.saveMobileApp(mobileApp); |
|||
this.config.deleteEntity = id => this.mobileAppService.deleteMobileApp(id.id); |
|||
} |
|||
|
|||
resolve(_route: ActivatedRouteSnapshot): EntityTableConfig<MobileApp> { |
|||
return this.config; |
|||
} |
|||
|
|||
private mobileStatus(status: MobileAppStatus): string { |
|||
const translateKey = mobileAppStatusTranslations.get(status); |
|||
let backgroundColor = 'rgba(25, 128, 56, 0.06)'; |
|||
switch (status) { |
|||
case MobileAppStatus.DEPRECATED: |
|||
backgroundColor = 'rgba(250, 164, 5, 0.06)'; |
|||
break; |
|||
case MobileAppStatus.SUSPENDED: |
|||
backgroundColor = 'rgba(209, 39, 48, 0.06)'; |
|||
break; |
|||
case MobileAppStatus.DRAFT: |
|||
backgroundColor = 'rgba(160, 160, 160, 0.06)'; |
|||
break; |
|||
} |
|||
return `<div style="border-radius: 14px; height: 28px; line-height: 20px; padding: 4px 10px;
|
|||
width: fit-content; background-color: ${backgroundColor}"> |
|||
${this.translate.instant(translateKey)} |
|||
</div>`;
|
|||
} |
|||
|
|||
private mobileStatusStyle(status: MobileAppStatus): object { |
|||
const styleObj = { |
|||
fontSize: '14px', |
|||
color: '#198038' |
|||
}; |
|||
switch (status) { |
|||
case MobileAppStatus.DEPRECATED: |
|||
styleObj.color = '#FAA405'; |
|||
break; |
|||
case MobileAppStatus.SUSPENDED: |
|||
styleObj.color = '#D12730'; |
|||
break; |
|||
case MobileAppStatus.DRAFT: |
|||
styleObj.color = '#A0A0A0'; |
|||
break; |
|||
} |
|||
return styleObj; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
/** |
|||
* Copyright © 2016-2024 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{ |
|||
width: 100000px; |
|||
|
|||
.tb-entity-title-container { |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
} |
|||
@ -0,0 +1,145 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2024 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. |
|||
|
|||
--> |
|||
<section class="mat-padding" [formGroup]="entityForm" fxLayout="column"> |
|||
<mat-form-field appearance="outline" subscriptSizing="dynamic"> |
|||
<mat-label translate>mobile.mobile-package</mat-label> |
|||
<input matInput required formControlName="pkgName"> |
|||
<tb-copy-button |
|||
matSuffix |
|||
miniButton="false" |
|||
[copyText]="entityForm.get('pkgName').value" |
|||
tooltipText="{{ 'mobile.copy-application-package' | translate }}" |
|||
tooltipPosition="above" |
|||
icon="content_copy"> |
|||
</tb-copy-button> |
|||
<mat-hint> </mat-hint> |
|||
<mat-error *ngIf="entityForm.get('pkgName').hasError('required')"> |
|||
{{ 'mobile.mobile-package-required' | translate }} |
|||
</mat-error> |
|||
<mat-error *ngIf="entityForm.get('pkgName').hasError('maxlength')"> |
|||
{{ 'mobile.mobile-package-max-length' | translate }} |
|||
</mat-error> |
|||
<mat-error *ngIf="entityForm.get('pkgName').hasError('pattern')"> |
|||
{{ 'mobile.mobile-package-spaces' | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
<mat-form-field appearance="outline" class="flex"> |
|||
<mat-label translate>mobile.platform-type</mat-label> |
|||
<mat-select formControlName="platformType"> |
|||
<mat-option *ngFor="let platformType of platformTypes" [value]="platformType"> |
|||
{{ platformTypeTranslations.get(platformType) | translate }} |
|||
</mat-option> |
|||
</mat-select> |
|||
</mat-form-field> |
|||
<mat-form-field appearance="outline" subscriptSizing="dynamic"> |
|||
<mat-label translate>mobile.application-secret</mat-label> |
|||
<input matInput required formControlName="appSecret" |
|||
placeholder="{{ 'mobile.set' | translate }}"> |
|||
<div matSuffix style="display: flex; align-items: center"> |
|||
<button mat-icon-button |
|||
type="button" |
|||
*ngIf="isEdit" |
|||
matTooltip="{{ 'mobile.generate-application-secret' | translate }}" |
|||
matTooltipPosition="above" |
|||
(click)="generateAppSecret($event)"> |
|||
<tb-icon>cached</tb-icon> |
|||
</button> |
|||
<tb-copy-button |
|||
miniButton="false" |
|||
[copyText]="entityForm.get('appSecret').value" |
|||
tooltipText="{{ 'mobile.copy-application-secret' | translate }}" |
|||
tooltipPosition="above" |
|||
icon="content_copy"> |
|||
</tb-copy-button> |
|||
</div> |
|||
<mat-hint> </mat-hint> |
|||
</mat-form-field> |
|||
<mat-form-field appearance="outline"> |
|||
<mat-label translate>mobile.status</mat-label> |
|||
<mat-select formControlName="status"> |
|||
<mat-option *ngFor="let mobileAppStatus of mobileAppStatuses" [value]="mobileAppStatus"> |
|||
{{ mobileAppStatusTranslations.get(mobileAppStatus) | translate }} |
|||
</mat-option> |
|||
</mat-select> |
|||
</mat-form-field> |
|||
<section class="tb-form-panel stroked no-padding-bottom" formGroupName="versionInfo" style="margin-bottom: 21px;"> |
|||
<div class="tb-form-panel-title" translate>mobile.version-information</div> |
|||
<section fxLayout="column"> |
|||
<mat-form-field appearance="outline"> |
|||
<mat-label translate>mobile.min-version</mat-label> |
|||
<input matInput formControlName="minVersion"> |
|||
</mat-form-field> |
|||
<mat-form-field appearance="outline"> |
|||
<mat-label translate>mobile.latest-version</mat-label> |
|||
<input matInput formControlName="latestVersion"> |
|||
</mat-form-field> |
|||
</section> |
|||
</section> |
|||
<section class="tb-form-panel stroked no-padding-bottom" formGroupName="storeInfo"> |
|||
<div class="tb-form-panel-title" translate>mobile.store-information</div> |
|||
<section fxLayout="column"> |
|||
<mat-form-field appearance="outline"> |
|||
<mat-label>{{ |
|||
(entityForm.get('platformType').value === PlatformType.ANDROID ? 'mobile.google-play-link' : 'mobile.app-store-link') | translate |
|||
}}</mat-label> |
|||
<input matInput [required]="entityForm.get('status').value === 'PUBLISHED'" formControlName="storeLink"> |
|||
<tb-copy-button |
|||
matSuffix |
|||
miniButton="false" |
|||
[copyText]="entityForm.get('storeInfo.storeLink').value" |
|||
tooltipText="{{ (entityForm.get('platformType').value === PlatformType.ANDROID ? 'mobile.copy-google-play-link' : 'mobile.copy-app-store-link') | translate }}" |
|||
tooltipPosition="above" |
|||
icon="content_copy"> |
|||
</tb-copy-button> |
|||
<mat-error *ngIf="entityForm.get('storeInfo.storeLink').hasError('required')"> |
|||
{{ (entityForm.get('platformType').value === PlatformType.ANDROID ? 'mobile.google-play-link-required' : 'mobile.app-store-link-required') | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
<mat-form-field appearance="outline" *ngIf="entityForm.get('platformType').value === PlatformType.ANDROID"> |
|||
<mat-label translate>mobile.sha256-certificate-fingerprints</mat-label> |
|||
<input matInput [required]="entityForm.get('status').value === MobileAppStatus.PUBLISHED" formControlName="sha256CertFingerprints"> |
|||
<tb-copy-button |
|||
matSuffix |
|||
miniButton="false" |
|||
[copyText]="entityForm.get('storeInfo.sha256CertFingerprints').value" |
|||
tooltipText="{{ 'mobile.copy-sha256-certificate-fingerprints' | translate }}" |
|||
tooltipPosition="above" |
|||
icon="content_copy"> |
|||
</tb-copy-button> |
|||
<mat-error *ngIf="entityForm.get('storeInfo.sha256CertFingerprints').hasError('required')"> |
|||
{{ 'mobile.sha256-certificate-fingerprints-required' | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
<mat-form-field appearance="outline" *ngIf="entityForm.get('platformType').value === PlatformType.IOS"> |
|||
<mat-label translate>mobile.app-id</mat-label> |
|||
<input matInput [required]="entityForm.get('status').value === MobileAppStatus.PUBLISHED" formControlName="appId"> |
|||
<tb-copy-button |
|||
matSuffix |
|||
miniButton="false" |
|||
[copyText]="entityForm.get('storeInfo.appId').value" |
|||
tooltipText="{{ 'mobile.copy-app-id' | translate }}" |
|||
tooltipPosition="above" |
|||
icon="content_copy"> |
|||
</tb-copy-button> |
|||
<mat-error *ngIf="entityForm.get('storeInfo.appId').hasError('required')"> |
|||
{{ 'mobile.app-id-required' | translate }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
</section> |
|||
</section> |
|||
</section> |
|||
@ -0,0 +1,18 @@ |
|||
/** |
|||
* Copyright © 2016-2024 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 { |
|||
--mdc-outlined-text-field-outline-color: rgba(0,0,0,0.12); |
|||
} |
|||
@ -0,0 +1,153 @@ |
|||
///
|
|||
/// Copyright © 2016-2024 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 { ChangeDetectorRef, Component, Inject } from '@angular/core'; |
|||
import { EntityComponent } from '@home/components/entity/entity.component'; |
|||
import { AppState } from '@core/core.state'; |
|||
import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; |
|||
import { TranslateService } from '@ngx-translate/core'; |
|||
import { Store } from '@ngrx/store'; |
|||
import { FormBuilder, FormGroup, UntypedFormControl, Validators } from '@angular/forms'; |
|||
import { randomAlphanumeric } from '@core/utils'; |
|||
import { EntityType } from '@shared/models/entity-type.models'; |
|||
import { MobileApp, MobileAppStatus, mobileAppStatusTranslations } from '@shared/models/mobile-app.models'; |
|||
import { PlatformType, platformTypeTranslations } from '@shared/models/oauth2.models'; |
|||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; |
|||
|
|||
@Component({ |
|||
selector: 'tb-mobile-app', |
|||
templateUrl: './mobile-app.component.html', |
|||
styleUrls: ['./mobile-app.component.scss'] |
|||
}) |
|||
export class MobileAppComponent extends EntityComponent<MobileApp> { |
|||
|
|||
entityType = EntityType; |
|||
|
|||
platformTypes = [PlatformType.ANDROID, PlatformType.IOS]; |
|||
|
|||
MobileAppStatus = MobileAppStatus; |
|||
PlatformType = PlatformType; |
|||
|
|||
mobileAppStatuses = Object.keys(MobileAppStatus) as MobileAppStatus[]; |
|||
|
|||
platformTypeTranslations = platformTypeTranslations; |
|||
mobileAppStatusTranslations = mobileAppStatusTranslations; |
|||
|
|||
constructor(protected store: Store<AppState>, |
|||
protected translate: TranslateService, |
|||
@Inject('entity') protected entityValue: MobileApp, |
|||
@Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig<MobileApp>, |
|||
protected cd: ChangeDetectorRef, |
|||
public fb: FormBuilder) { |
|||
super(store, fb, entityValue, entitiesTableConfigValue, cd); |
|||
} |
|||
|
|||
buildForm(entity: MobileApp): FormGroup { |
|||
const form = this.fb.group({ |
|||
pkgName: [entity?.pkgName ? entity.pkgName : '', [Validators.required, Validators.maxLength(255), |
|||
Validators.pattern(/^\S+$/)]], |
|||
platformType: [entity?.platformType ? entity.platformType : PlatformType.ANDROID], |
|||
appSecret: [entity?.appSecret ? entity.appSecret : btoa(randomAlphanumeric(64)), [Validators.required, this.base64Format]], |
|||
status: [entity?.status ? entity.status : MobileAppStatus.DRAFT], |
|||
versionInfo: this.fb.group({ |
|||
minVersion: [entity?.versionInfo?.minVersion ? entity.versionInfo.minVersion : ''], |
|||
latestVersion: [entity?.versionInfo?.latestVersion ? entity.versionInfo.latestVersion : ''], |
|||
}), |
|||
storeInfo: this.fb.group({ |
|||
storeLink: [entity?.storeInfo?.storeLink ? entity.storeInfo.storeLink : ''], |
|||
sha256CertFingerprints: [entity?.storeInfo?.sha256CertFingerprints ? entity.storeInfo.sha256CertFingerprints : ''], |
|||
appId: [entity?.storeInfo?.appId ? entity.storeInfo.appId : ''], |
|||
}), |
|||
}); |
|||
|
|||
form.get('platformType').valueChanges.pipe( |
|||
takeUntilDestroyed() |
|||
).subscribe((value: PlatformType) => { |
|||
if (value === PlatformType.ANDROID) { |
|||
form.get('storeInfo.sha256CertFingerprints').enable({emitEvent: false}); |
|||
form.get('storeInfo.appId').disable({emitEvent: false}); |
|||
} else if (value === PlatformType.IOS) { |
|||
form.get('storeInfo.sha256CertFingerprints').disable({emitEvent: false}); |
|||
form.get('storeInfo.appId').enable({emitEvent: false}); |
|||
} |
|||
form.get('storeInfo.storeLink').setValue('', {emitEvent: false}); |
|||
}); |
|||
|
|||
form.get('status').valueChanges.pipe( |
|||
takeUntilDestroyed() |
|||
).subscribe((value: MobileAppStatus) => { |
|||
if (value === MobileAppStatus.PUBLISHED) { |
|||
form.get('storeInfo.storeLink').addValidators(Validators.required); |
|||
form.get('storeInfo.sha256CertFingerprints').addValidators(Validators.required); |
|||
form.get('storeInfo.appId').addValidators(Validators.required); |
|||
} else { |
|||
form.get('storeInfo.storeLink').clearValidators(); |
|||
form.get('storeInfo.sha256CertFingerprints').clearValidators(); |
|||
form.get('storeInfo.appId').clearValidators(); |
|||
} |
|||
form.get('storeInfo.storeLink').updateValueAndValidity({emitEvent: false}); |
|||
form.get('storeInfo.sha256CertFingerprints').updateValueAndValidity({emitEvent: false}); |
|||
form.get('storeInfo.appId').updateValueAndValidity({emitEvent: false}); |
|||
}); |
|||
|
|||
return form; |
|||
} |
|||
|
|||
updateForm(entity: MobileApp) { |
|||
this.entityForm.patchValue(entity, {emitEvent: false}); |
|||
} |
|||
|
|||
override updateFormState(): void { |
|||
super.updateFormState(); |
|||
if (this.isEdit && this.entityForm && !this.isAdd) { |
|||
this.entityForm.get('platformType').disable({emitEvent: false}); |
|||
if (this.entityForm.get('platformType').value === PlatformType.ANDROID) { |
|||
this.entityForm.get('storeInfo.appId').disable({emitEvent: false}); |
|||
} else if (this.entityForm.get('platformType').value === PlatformType.IOS) { |
|||
this.entityForm.get('storeInfo.sha256CertFingerprints').disable({emitEvent: false}); |
|||
} |
|||
} |
|||
if (this.entityForm && this.isAdd) { |
|||
this.entityForm.get('storeInfo.appId').disable({emitEvent: false}); |
|||
} |
|||
} |
|||
|
|||
override prepareFormValue(value: MobileApp): MobileApp { |
|||
value.storeInfo = this.entityForm.get('storeInfo').value; |
|||
return super.prepareFormValue(value); |
|||
} |
|||
|
|||
generateAppSecret($event: Event) { |
|||
$event.stopPropagation(); |
|||
this.entityForm.get('appSecret').setValue(btoa(randomAlphanumeric(64))); |
|||
this.entityForm.get('appSecret').markAsDirty(); |
|||
} |
|||
|
|||
private base64Format(control: UntypedFormControl): { [key: string]: boolean } | null { |
|||
if (control.value === '') { |
|||
return null; |
|||
} |
|||
try { |
|||
const value = atob(control.value); |
|||
if (value.length < 64) { |
|||
return {minLength: true}; |
|||
} |
|||
return null; |
|||
} catch (e) { |
|||
return {base64: true}; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,78 @@ |
|||
///
|
|||
/// Copyright © 2016-2024 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 { RouterModule, Routes } from '@angular/router'; |
|||
import { RouterTabsComponent } from '@home/components/router-tabs.component'; |
|||
import { Authority } from '@shared/models/authority.enum'; |
|||
import { MenuId } from '@core/services/menu.models'; |
|||
import { MobileAppTableConfigResolver } from '@home/pages/mobile/applications/mobile-app-table-config.resolver'; |
|||
import { MobileAppSettingsComponent } from '@home/pages/admin/mobile-app-settings.component'; |
|||
import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; |
|||
import { applicationsRoutes } from '@home/pages/mobile/applications/applications-routing.module'; |
|||
|
|||
const routes: Routes = [ |
|||
{ |
|||
path: 'mobile-center', |
|||
component: RouterTabsComponent, |
|||
data: { |
|||
auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN], |
|||
breadcrumb: { |
|||
menuId: MenuId.mobile_center |
|||
} |
|||
}, |
|||
children: [ |
|||
{ |
|||
path: '', |
|||
children: [], |
|||
data: { |
|||
auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER, Authority.SYS_ADMIN], |
|||
redirectTo: '/mobile-center/applications' |
|||
} |
|||
}, |
|||
...applicationsRoutes, |
|||
{ |
|||
path: 'mobile-app', |
|||
component: MobileAppSettingsComponent, |
|||
canDeactivate: [ConfirmOnExitGuard], |
|||
data: { |
|||
auth: [Authority.SYS_ADMIN], |
|||
title: 'admin.mobile-app.mobile-app', |
|||
breadcrumb: { |
|||
menuId: MenuId.mobile_app_settings |
|||
} |
|||
} |
|||
} |
|||
] |
|||
} |
|||
]; |
|||
|
|||
routes.push( |
|||
{ |
|||
path: 'security-settings/oauth2/mobile-applications', |
|||
pathMatch: 'full', |
|||
redirectTo: '/mobile-center/applications' |
|||
} |
|||
); |
|||
|
|||
@NgModule({ |
|||
providers: [ |
|||
MobileAppTableConfigResolver |
|||
], |
|||
imports: [RouterModule.forChild(routes)], |
|||
exports: [RouterModule] |
|||
}) |
|||
export class MobileRoutingModule { } |
|||
@ -0,0 +1,33 @@ |
|||
///
|
|||
/// Copyright © 2016-2024 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 { SharedModule } from '@shared/shared.module'; |
|||
import { HomeComponentsModule } from '@home/components/home-components.module'; |
|||
import { MobileRoutingModule } from '@home/pages/mobile/mobile-routing.module'; |
|||
import { ApplicationModule } from '@home/pages/mobile/applications/applications.module'; |
|||
|
|||
@NgModule({ |
|||
imports: [ |
|||
CommonModule, |
|||
SharedModule, |
|||
HomeComponentsModule, |
|||
ApplicationModule, |
|||
MobileRoutingModule, |
|||
] |
|||
}) |
|||
export class MobileModule { } |
|||
@ -0,0 +1,27 @@ |
|||
///
|
|||
/// Copyright © 2016-2024 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 '@shared/models/id/entity-id'; |
|||
import { EntityType } from '@shared/models/entity-type.models'; |
|||
|
|||
export class MobileAppBundleId implements EntityId { |
|||
entityType = EntityType.MOBILE_APP_BUNDLE; |
|||
id: string; |
|||
|
|||
constructor(id: string) { |
|||
this.id = id; |
|||
} |
|||
} |
|||
Loading…
Reference in new issue