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