43 changed files with 1968 additions and 143 deletions
@ -0,0 +1,40 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2019 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.common.data; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import org.thingsboard.server.common.data.id.DeviceId; |
||||
|
|
||||
|
@Data |
||||
|
public class DeviceInfo extends Device { |
||||
|
|
||||
|
private String customerTitle; |
||||
|
private boolean customerIsPublic; |
||||
|
|
||||
|
public DeviceInfo() { |
||||
|
super(); |
||||
|
} |
||||
|
|
||||
|
public DeviceInfo(DeviceId deviceId) { |
||||
|
super(deviceId); |
||||
|
} |
||||
|
|
||||
|
public DeviceInfo(Device device, String customerTitle, boolean customerIsPublic) { |
||||
|
super(device); |
||||
|
this.customerTitle = customerTitle; |
||||
|
this.customerIsPublic = customerIsPublic; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,122 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2019 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.dao.model.sql; |
||||
|
|
||||
|
import com.datastax.driver.core.utils.UUIDs; |
||||
|
import com.fasterxml.jackson.databind.JsonNode; |
||||
|
import lombok.Data; |
||||
|
import lombok.EqualsAndHashCode; |
||||
|
import org.hibernate.annotations.Type; |
||||
|
import org.hibernate.annotations.TypeDef; |
||||
|
import org.thingsboard.server.common.data.Device; |
||||
|
import org.thingsboard.server.common.data.id.CustomerId; |
||||
|
import org.thingsboard.server.common.data.id.DeviceId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.dao.model.BaseSqlEntity; |
||||
|
import org.thingsboard.server.dao.model.ModelConstants; |
||||
|
import org.thingsboard.server.dao.model.SearchTextEntity; |
||||
|
import org.thingsboard.server.dao.util.mapping.JsonStringType; |
||||
|
|
||||
|
import javax.persistence.Column; |
||||
|
import javax.persistence.Entity; |
||||
|
import javax.persistence.MappedSuperclass; |
||||
|
|
||||
|
@Data |
||||
|
@EqualsAndHashCode(callSuper = true) |
||||
|
@TypeDef(name = "json", typeClass = JsonStringType.class) |
||||
|
@MappedSuperclass |
||||
|
public abstract class AbstractDeviceEntity<T extends Device> extends BaseSqlEntity<T> implements SearchTextEntity<T> { |
||||
|
|
||||
|
@Column(name = ModelConstants.DEVICE_TENANT_ID_PROPERTY) |
||||
|
private String tenantId; |
||||
|
|
||||
|
@Column(name = ModelConstants.DEVICE_CUSTOMER_ID_PROPERTY) |
||||
|
private String customerId; |
||||
|
|
||||
|
@Column(name = ModelConstants.DEVICE_TYPE_PROPERTY) |
||||
|
private String type; |
||||
|
|
||||
|
@Column(name = ModelConstants.DEVICE_NAME_PROPERTY) |
||||
|
private String name; |
||||
|
|
||||
|
@Column(name = ModelConstants.DEVICE_LABEL_PROPERTY) |
||||
|
private String label; |
||||
|
|
||||
|
@Column(name = ModelConstants.SEARCH_TEXT_PROPERTY) |
||||
|
private String searchText; |
||||
|
|
||||
|
@Type(type = "json") |
||||
|
@Column(name = ModelConstants.DEVICE_ADDITIONAL_INFO_PROPERTY) |
||||
|
private JsonNode additionalInfo; |
||||
|
|
||||
|
public AbstractDeviceEntity() { |
||||
|
super(); |
||||
|
} |
||||
|
|
||||
|
public AbstractDeviceEntity(Device device) { |
||||
|
if (device.getId() != null) { |
||||
|
this.setId(device.getId().getId()); |
||||
|
} |
||||
|
if (device.getTenantId() != null) { |
||||
|
this.tenantId = toString(device.getTenantId().getId()); |
||||
|
} |
||||
|
if (device.getCustomerId() != null) { |
||||
|
this.customerId = toString(device.getCustomerId().getId()); |
||||
|
} |
||||
|
this.name = device.getName(); |
||||
|
this.type = device.getType(); |
||||
|
this.label = device.getLabel(); |
||||
|
this.additionalInfo = device.getAdditionalInfo(); |
||||
|
} |
||||
|
|
||||
|
public AbstractDeviceEntity(DeviceEntity deviceEntity) { |
||||
|
this.setId(deviceEntity.getId());; |
||||
|
this.tenantId = deviceEntity.getTenantId(); |
||||
|
this.customerId = deviceEntity.getCustomerId(); |
||||
|
this.type = deviceEntity.getType(); |
||||
|
this.name = deviceEntity.getName(); |
||||
|
this.label = deviceEntity.getLabel(); |
||||
|
this.searchText = deviceEntity.getSearchText(); |
||||
|
this.additionalInfo = deviceEntity.getAdditionalInfo(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String getSearchTextSource() { |
||||
|
return name; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void setSearchText(String searchText) { |
||||
|
this.searchText = searchText; |
||||
|
} |
||||
|
|
||||
|
protected Device toDevice() { |
||||
|
Device device = new Device(new DeviceId(getId())); |
||||
|
device.setCreatedTime(UUIDs.unixTimestamp(getId())); |
||||
|
if (tenantId != null) { |
||||
|
device.setTenantId(new TenantId(toUUID(tenantId))); |
||||
|
} |
||||
|
if (customerId != null) { |
||||
|
device.setCustomerId(new CustomerId(toUUID(customerId))); |
||||
|
} |
||||
|
device.setName(name); |
||||
|
device.setType(type); |
||||
|
device.setLabel(label); |
||||
|
device.setAdditionalInfo(additionalInfo); |
||||
|
return device; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,58 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2019 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. |
||||
|
*/ |
||||
|
package org.thingsboard.server.dao.model.sql; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import lombok.EqualsAndHashCode; |
||||
|
import com.fasterxml.jackson.databind.JsonNode; |
||||
|
import org.thingsboard.server.common.data.DeviceInfo; |
||||
|
|
||||
|
import java.util.HashMap; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
@Data |
||||
|
@EqualsAndHashCode(callSuper = true) |
||||
|
public class DeviceInfoEntity extends AbstractDeviceEntity<DeviceInfo> { |
||||
|
|
||||
|
public static final Map<String,String> deviceInfoColumnMap = new HashMap<>(); |
||||
|
static { |
||||
|
deviceInfoColumnMap.put("customerTitle", "c.title"); |
||||
|
} |
||||
|
|
||||
|
private String customerTitle; |
||||
|
private boolean customerIsPublic; |
||||
|
|
||||
|
public DeviceInfoEntity() { |
||||
|
super(); |
||||
|
} |
||||
|
|
||||
|
public DeviceInfoEntity(DeviceEntity deviceEntity, |
||||
|
String customerTitle, |
||||
|
Object customerAdditionalInfo) { |
||||
|
super(deviceEntity); |
||||
|
this.customerTitle = customerTitle; |
||||
|
if (customerAdditionalInfo != null && ((JsonNode)customerAdditionalInfo).has("isPublic")) { |
||||
|
this.customerIsPublic = ((JsonNode)customerAdditionalInfo).get("isPublic").asBoolean(); |
||||
|
} else { |
||||
|
this.customerIsPublic = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public DeviceInfo toData() { |
||||
|
return new DeviceInfo(super.toDevice(), customerTitle, customerIsPublic); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,74 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2019 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 { defaultHttpOptions } from './http-utils'; |
||||
|
import { Observable } from 'rxjs/index'; |
||||
|
import { HttpClient } from '@angular/common/http'; |
||||
|
import { PageLink } from '@shared/models/page/page-link'; |
||||
|
import { PageData } from '@shared/models/page/page-data'; |
||||
|
import { Tenant } from '@shared/models/tenant.model'; |
||||
|
import {DashboardInfo, Dashboard} from '@shared/models/dashboard.models'; |
||||
|
import {map} from 'rxjs/operators'; |
||||
|
import {DeviceInfo, Device} from '@app/shared/models/device.models'; |
||||
|
import {EntitySubtype} from '@app/shared/models/entity-type.models'; |
||||
|
|
||||
|
@Injectable({ |
||||
|
providedIn: 'root' |
||||
|
}) |
||||
|
export class DeviceService { |
||||
|
|
||||
|
constructor( |
||||
|
private http: HttpClient |
||||
|
) { } |
||||
|
|
||||
|
public getTenantDeviceInfos(pageLink: PageLink, type: string = '', ignoreErrors: boolean = false, |
||||
|
ignoreLoading: boolean = false): Observable<PageData<DeviceInfo>> { |
||||
|
return this.http.get<PageData<DeviceInfo>>(`/api/tenant/deviceInfos${pageLink.toQuery()}&type=${type}`, |
||||
|
defaultHttpOptions(ignoreLoading, ignoreErrors)); |
||||
|
} |
||||
|
|
||||
|
public getCustomerDeviceInfos(customerId: string, pageLink: PageLink, type: string = '', ignoreErrors: boolean = false, |
||||
|
ignoreLoading: boolean = false): Observable<PageData<DeviceInfo>> { |
||||
|
return this.http.get<PageData<DeviceInfo>>(`/api/customer/${customerId}/deviceInfos${pageLink.toQuery()}&type=${type}`, |
||||
|
defaultHttpOptions(ignoreLoading, ignoreErrors)); |
||||
|
} |
||||
|
|
||||
|
public getDevice(deviceId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Device> { |
||||
|
return this.http.get<Device>(`/api/device/${deviceId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); |
||||
|
} |
||||
|
|
||||
|
public getDeviceInfo(deviceId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<DeviceInfo> { |
||||
|
return this.http.get<DeviceInfo>(`/api/device/info/${deviceId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); |
||||
|
} |
||||
|
|
||||
|
public saveDevice(device: Device, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Device> { |
||||
|
return this.http.post<Device>('/api/device', device, defaultHttpOptions(ignoreLoading, ignoreErrors)); |
||||
|
} |
||||
|
|
||||
|
public deleteDevice(deviceId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { |
||||
|
return this.http.delete(`/api/device/${deviceId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); |
||||
|
} |
||||
|
|
||||
|
public getDeviceTypes(ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntitySubtype>> { |
||||
|
return this.http.get<Array<EntitySubtype>>('/api/device/types', defaultHttpOptions(ignoreLoading, ignoreErrors)); |
||||
|
} |
||||
|
|
||||
|
public unassignDeviceFromCustomer(deviceId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { |
||||
|
return this.http.delete(`/api/customer/device/${deviceId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2019 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.
|
||||
|
///
|
||||
|
|
||||
|
|
||||
|
export interface BroadcastMessage { |
||||
|
name: string; |
||||
|
args?: Array<any>; |
||||
|
} |
||||
|
|
||||
|
export interface BroadcastEvent { |
||||
|
name: string; |
||||
|
} |
||||
|
|
||||
|
export type BroadcastListener = (event: BroadcastEvent, ...args: Array<any>) => void; |
||||
@ -0,0 +1,51 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2019 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 {Subject, Subscription} from 'rxjs'; |
||||
|
import {NotificationMessage} from '@core/notification/notification.models'; |
||||
|
import {BroadcastEvent, BroadcastListener, BroadcastMessage} from '@core/services/broadcast.models'; |
||||
|
import {filter} from 'rxjs/operators'; |
||||
|
|
||||
|
@Injectable({ |
||||
|
providedIn: 'root' |
||||
|
}) |
||||
|
export class BroadcastService { |
||||
|
|
||||
|
private broadcastSubject: Subject<BroadcastMessage> = new Subject(); |
||||
|
|
||||
|
broadcast(name: string, ...args: Array<any>) { |
||||
|
const message = { |
||||
|
name, |
||||
|
args |
||||
|
} as BroadcastMessage; |
||||
|
this.broadcastSubject.next(message); |
||||
|
} |
||||
|
|
||||
|
on(name: string, listener: BroadcastListener): Subscription { |
||||
|
return this.broadcastSubject.asObservable().pipe( |
||||
|
filter((message) => message.name === name) |
||||
|
).subscribe( |
||||
|
(message) => { |
||||
|
const event = { |
||||
|
name: message.name |
||||
|
} as BroadcastEvent; |
||||
|
listener(event, message.args); |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,50 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2019 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 {EntitiesTableComponent} from '@shared/components/entity/entities-table.component'; |
||||
|
import {Authority} from '@shared/models/authority.enum'; |
||||
|
import {DevicesTableConfigResolver} from '@modules/home/pages/device/devices-table-config.resolver'; |
||||
|
|
||||
|
const routes: Routes = [ |
||||
|
{ |
||||
|
path: 'devices', |
||||
|
component: EntitiesTableComponent, |
||||
|
data: { |
||||
|
auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], |
||||
|
title: 'device.devices', |
||||
|
devicesType: 'tenant', |
||||
|
breadcrumb: { |
||||
|
label: 'device.devices', |
||||
|
icon: 'devices_other' |
||||
|
} |
||||
|
}, |
||||
|
resolve: { |
||||
|
entitiesTableConfig: DevicesTableConfigResolver |
||||
|
} |
||||
|
} |
||||
|
]; |
||||
|
|
||||
|
@NgModule({ |
||||
|
imports: [RouterModule.forChild(routes)], |
||||
|
exports: [RouterModule], |
||||
|
providers: [ |
||||
|
DevicesTableConfigResolver |
||||
|
] |
||||
|
}) |
||||
|
export class DeviceRoutingModule { } |
||||
@ -0,0 +1,23 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2019 The Thingsboard Authors |
||||
|
|
||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
you may not use this file except in compliance with the License. |
||||
|
You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
|
||||
|
--> |
||||
|
<tb-entity-subtype-select |
||||
|
[showLabel]="true" |
||||
|
[entityType]="entityType.DEVICE" |
||||
|
[ngModel]="entitiesTableConfig.componentsData.deviceType" |
||||
|
(ngModelChange)="deviceTypeChanged($event)"> |
||||
|
</tb-entity-subtype-select> |
||||
@ -0,0 +1,36 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2019 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 { |
||||
|
flex: 1; |
||||
|
display: flex; |
||||
|
justify-content: flex-start; |
||||
|
} |
||||
|
|
||||
|
:host ::ng-deep { |
||||
|
tb-entity-subtype-select { |
||||
|
mat-form-field { |
||||
|
font-size: 16px; |
||||
|
|
||||
|
.mat-form-field-wrapper { |
||||
|
padding-bottom: 0; |
||||
|
} |
||||
|
|
||||
|
.mat-form-field-underline { |
||||
|
bottom: 0; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2019 The Thingsboard Authors
|
||||
|
///
|
||||
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
/// you may not use this file except in compliance with the License.
|
||||
|
/// You may obtain a copy of the License at
|
||||
|
///
|
||||
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
///
|
||||
|
/// Unless required by applicable law or agreed to in writing, software
|
||||
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
/// See the License for the specific language governing permissions and
|
||||
|
/// limitations under the License.
|
||||
|
///
|
||||
|
|
||||
|
import { Component } from '@angular/core'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { EntityTableHeaderComponent } from '@shared/components/entity/entity-table-header.component'; |
||||
|
import {DeviceInfo} from '@app/shared/models/device.models'; |
||||
|
import {EntityType} from '@shared/models/entity-type.models'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-device-table-header', |
||||
|
templateUrl: './device-table-header.component.html', |
||||
|
styleUrls: ['./device-table-header.component.scss'] |
||||
|
}) |
||||
|
export class DeviceTableHeaderComponent extends EntityTableHeaderComponent<DeviceInfo> { |
||||
|
|
||||
|
entityType = EntityType; |
||||
|
|
||||
|
constructor(protected store: Store<AppState>) { |
||||
|
super(store); |
||||
|
} |
||||
|
|
||||
|
deviceTypeChanged(deviceType: string) { |
||||
|
this.entitiesTableConfig.componentsData.deviceType = deviceType; |
||||
|
this.entitiesTableConfig.table.updateData(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,107 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2019 The Thingsboard Authors |
||||
|
|
||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
you may not use this file except in compliance with the License. |
||||
|
You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
|
||||
|
--> |
||||
|
<div class="tb-details-buttons"> |
||||
|
<button mat-raised-button color="primary" |
||||
|
[disabled]="(isLoading$ | async)" |
||||
|
(click)="onEntityAction($event, 'makePublic')" |
||||
|
[fxShow]="!isEdit && deviceScope === 'tenant' && !isAssignedToCustomer(entity) && !entity?.customerIsPublic"> |
||||
|
{{'device.make-public' | translate }} |
||||
|
</button> |
||||
|
<button mat-raised-button color="primary" |
||||
|
[disabled]="(isLoading$ | async)" |
||||
|
(click)="onEntityAction($event, 'assignToCustomer')" |
||||
|
[fxShow]="!isEdit && deviceScope === 'tenant' && !isAssignedToCustomer(entity)"> |
||||
|
{{'device.assign-to-customer' | translate }} |
||||
|
</button> |
||||
|
<button mat-raised-button color="primary" |
||||
|
[disabled]="(isLoading$ | async)" |
||||
|
(click)="onEntityAction($event, 'unassignFromCustomer')" |
||||
|
[fxShow]="!isEdit && (deviceScope === 'customer' || deviceScope === 'tenant') && isAssignedToCustomer(entity)"> |
||||
|
{{ (entity?.customerIsPublic ? 'device.make-private' : 'device.unassign-from-customer') | translate }} |
||||
|
</button> |
||||
|
<button mat-raised-button color="primary" |
||||
|
[disabled]="(isLoading$ | async)" |
||||
|
(click)="onEntityAction($event, 'manageCredentials')" |
||||
|
[fxShow]="!isEdit"> |
||||
|
{{ (deviceScope === 'customer_user' ? 'device.view-credentials' : 'device.manage-credentials') | translate }} |
||||
|
</button> |
||||
|
<button mat-raised-button color="primary" |
||||
|
[disabled]="(isLoading$ | async)" |
||||
|
(click)="onEntityAction($event, 'delete')" |
||||
|
[fxShow]="!hideDelete() && !isEdit"> |
||||
|
{{'device.delete' | translate }} |
||||
|
</button> |
||||
|
<div fxLayout="row"> |
||||
|
<button mat-raised-button |
||||
|
ngxClipboard |
||||
|
(cbOnSuccess)="onDeviceIdCopied($event)" |
||||
|
[cbContent]="entity?.id?.id" |
||||
|
[fxShow]="!isEdit"> |
||||
|
<mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon> |
||||
|
<span translate>device.copyId</span> |
||||
|
</button> |
||||
|
<button mat-raised-button |
||||
|
(click)="copyAccessToken($event)" |
||||
|
[fxShow]="!isEdit"> |
||||
|
<mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon> |
||||
|
<span translate>device.copyAccessToken</span> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="mat-padding" fxLayout="column"> |
||||
|
<mat-form-field class="mat-block" |
||||
|
[fxShow]="!isEdit && isAssignedToCustomer(entity) |
||||
|
&& !entity?.customerIsPublic && deviceScope === 'tenant'"> |
||||
|
<mat-label translate>device.assignedToCustomer</mat-label> |
||||
|
<input matInput disabled [ngModel]="entity?.customerTitle"> |
||||
|
</mat-form-field> |
||||
|
<div class="tb-small" style="padding-bottom: 10px; padding-left: 2px;" |
||||
|
[fxShow]="!isEdit && entity?.customerIsPublic && (deviceScope === 'customer' || deviceScope === 'tenant')"> |
||||
|
{{ 'device.device-public' | translate }} |
||||
|
</div> |
||||
|
<form #entityNgForm="ngForm" [formGroup]="entityForm"> |
||||
|
<fieldset [disabled]="(isLoading$ | async) || !isEdit"> |
||||
|
<mat-form-field class="mat-block"> |
||||
|
<mat-label translate>device.name</mat-label> |
||||
|
<input matInput formControlName="name" required> |
||||
|
<mat-error *ngIf="entityForm.get('name').hasError('required')"> |
||||
|
{{ 'device.name-required' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<tb-entity-subtype-autocomplete |
||||
|
formControlName="type" |
||||
|
[required]="true" |
||||
|
[entityType]="entityType.DEVICE" |
||||
|
> |
||||
|
</tb-entity-subtype-autocomplete> |
||||
|
<mat-form-field class="mat-block"> |
||||
|
<mat-label translate>device.label</mat-label> |
||||
|
<input matInput formControlName="label"> |
||||
|
</mat-form-field> |
||||
|
<div formGroupName="additionalInfo" fxLayout="column"> |
||||
|
<mat-checkbox fxFlex formControlName="gateway"> |
||||
|
{{ 'device.is-gateway' | translate }} |
||||
|
</mat-checkbox> |
||||
|
<mat-form-field class="mat-block"> |
||||
|
<mat-label translate>device.description</mat-label> |
||||
|
<textarea matInput formControlName="description" rows="2"></textarea> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
</fieldset> |
||||
|
</form> |
||||
|
</div> |
||||
@ -0,0 +1,19 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2019 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 { |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,88 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2019 The Thingsboard Authors
|
||||
|
///
|
||||
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
/// you may not use this file except in compliance with the License.
|
||||
|
/// You may obtain a copy of the License at
|
||||
|
///
|
||||
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
///
|
||||
|
/// Unless required by applicable law or agreed to in writing, software
|
||||
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
/// See the License for the specific language governing permissions and
|
||||
|
/// limitations under the License.
|
||||
|
///
|
||||
|
|
||||
|
import { Component, OnInit } from '@angular/core'; |
||||
|
import { select, Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { EntityComponent } from '@shared/components/entity/entity.component'; |
||||
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; |
||||
|
import { User } from '@shared/models/user.model'; |
||||
|
import { selectAuth, selectUserDetails } from '@core/auth/auth.selectors'; |
||||
|
import { map } from 'rxjs/operators'; |
||||
|
import { Authority } from '@shared/models/authority.enum'; |
||||
|
import {DeviceInfo} from '@shared/models/device.models'; |
||||
|
import {EntityType} from '@shared/models/entity-type.models'; |
||||
|
import {NULL_UUID} from '@shared/models/id/has-uuid'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-device', |
||||
|
templateUrl: './device.component.html', |
||||
|
styleUrls: ['./device.component.scss'] |
||||
|
}) |
||||
|
export class DeviceComponent extends EntityComponent<DeviceInfo> { |
||||
|
|
||||
|
entityType = EntityType; |
||||
|
|
||||
|
deviceScope: 'tenant' | 'customer' | 'customer_user'; |
||||
|
|
||||
|
constructor(protected store: Store<AppState>, |
||||
|
public fb: FormBuilder) { |
||||
|
super(store); |
||||
|
} |
||||
|
|
||||
|
ngOnInit() { |
||||
|
this.deviceScope = this.entitiesTableConfig.componentsData.deviceScope; |
||||
|
super.ngOnInit(); |
||||
|
} |
||||
|
|
||||
|
hideDelete() { |
||||
|
if (this.entitiesTableConfig) { |
||||
|
return !this.entitiesTableConfig.deleteEnabled(this.entity); |
||||
|
} else { |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
isAssignedToCustomer(entity: DeviceInfo): boolean { |
||||
|
return entity && entity.customerId && entity.customerId.id !== NULL_UUID; |
||||
|
} |
||||
|
|
||||
|
buildForm(entity: DeviceInfo): FormGroup { |
||||
|
return this.fb.group( |
||||
|
{ |
||||
|
name: [entity ? entity.name : '', [Validators.required]], |
||||
|
type: [entity ? entity.type : null, [Validators.required]], |
||||
|
label: [entity ? entity.label : ''], |
||||
|
additionalInfo: this.fb.group( |
||||
|
{ |
||||
|
gateway: [entity && entity.additionalInfo ? entity.additionalInfo.gateway : false], |
||||
|
description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], |
||||
|
} |
||||
|
) |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
updateForm(entity: DeviceInfo) { |
||||
|
this.entityForm.patchValue({name: entity.name}); |
||||
|
this.entityForm.patchValue({type: entity.type}); |
||||
|
this.entityForm.patchValue({label: entity.label}); |
||||
|
this.entityForm.patchValue({additionalInfo: |
||||
|
{gateway: entity.additionalInfo ? entity.additionalInfo.gateway : false}}); |
||||
|
this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}}); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2019 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 {DeviceComponent} from '@modules/home/pages/device/device.component'; |
||||
|
import {DeviceRoutingModule} from './device-routing.module'; |
||||
|
import {DeviceTableHeaderComponent} from '@modules/home/pages/device/device-table-header.component'; |
||||
|
|
||||
|
@NgModule({ |
||||
|
entryComponents: [ |
||||
|
DeviceComponent, |
||||
|
DeviceTableHeaderComponent |
||||
|
], |
||||
|
declarations: [ |
||||
|
DeviceComponent, |
||||
|
DeviceTableHeaderComponent |
||||
|
], |
||||
|
imports: [ |
||||
|
CommonModule, |
||||
|
SharedModule, |
||||
|
DeviceRoutingModule |
||||
|
] |
||||
|
}) |
||||
|
export class DeviceModule { } |
||||
@ -0,0 +1,196 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2019 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, Resolve, Router} from '@angular/router'; |
||||
|
|
||||
|
import { Tenant } from '@shared/models/tenant.model'; |
||||
|
import { |
||||
|
CellActionDescriptor, |
||||
|
checkBoxCell, |
||||
|
DateEntityTableColumn, |
||||
|
EntityTableColumn, |
||||
|
EntityTableConfig |
||||
|
} from '@shared/components/entity/entities-table-config.models'; |
||||
|
import { TenantService } from '@core/http/tenant.service'; |
||||
|
import { TranslateService } from '@ngx-translate/core'; |
||||
|
import { DatePipe } from '@angular/common'; |
||||
|
import { |
||||
|
EntityType, |
||||
|
entityTypeResources, |
||||
|
entityTypeTranslations |
||||
|
} from '@shared/models/entity-type.models'; |
||||
|
import { TenantComponent } from '@modules/home/pages/tenant/tenant.component'; |
||||
|
import { EntityAction } from '@shared/components/entity/entity-component.models'; |
||||
|
import { User } from '@shared/models/user.model'; |
||||
|
import {Device, DeviceInfo} from '@app/shared/models/device.models'; |
||||
|
import {DeviceComponent} from '@modules/home/pages/device/device.component'; |
||||
|
import {Observable, of} from 'rxjs'; |
||||
|
import {select, Store} from '@ngrx/store'; |
||||
|
import {selectAuth, selectAuthUser} from '@core/auth/auth.selectors'; |
||||
|
import {map, mergeMap, take, tap} from 'rxjs/operators'; |
||||
|
import {AppState} from '@core/core.state'; |
||||
|
import {DeviceService} from '@app/core/http/device.service'; |
||||
|
import {Authority} from '@app/shared/models/authority.enum'; |
||||
|
import {CustomerService} from '@core/http/customer.service'; |
||||
|
import {Customer} from '@app/shared/models/customer.model'; |
||||
|
import {NULL_UUID} from '@shared/models/id/has-uuid'; |
||||
|
import {BroadcastService} from '@core/services/broadcast.service'; |
||||
|
import {DeviceTableHeaderComponent} from '@modules/home/pages/device/device-table-header.component'; |
||||
|
|
||||
|
@Injectable() |
||||
|
export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<DeviceInfo>> { |
||||
|
|
||||
|
private readonly config: EntityTableConfig<DeviceInfo> = new EntityTableConfig<DeviceInfo>(); |
||||
|
|
||||
|
private customerId: string; |
||||
|
|
||||
|
constructor(private store: Store<AppState>, |
||||
|
private broadcast: BroadcastService, |
||||
|
private deviceService: DeviceService, |
||||
|
private customerService: CustomerService, |
||||
|
private translate: TranslateService, |
||||
|
private datePipe: DatePipe, |
||||
|
private router: Router) { |
||||
|
|
||||
|
this.config.entityType = EntityType.CUSTOMER; |
||||
|
this.config.entityComponent = DeviceComponent; |
||||
|
this.config.entityTranslations = entityTypeTranslations.get(EntityType.DEVICE); |
||||
|
this.config.entityResources = entityTypeResources.get(EntityType.DEVICE); |
||||
|
|
||||
|
this.config.deleteEntityTitle = device => this.translate.instant('device.delete-device-title', { deviceName: device.name }); |
||||
|
this.config.deleteEntityContent = () => this.translate.instant('device.delete-device-text'); |
||||
|
this.config.deleteEntitiesTitle = count => this.translate.instant('device.delete-devices-title', {count}); |
||||
|
this.config.deleteEntitiesContent = () => this.translate.instant('device.delete-devices-text'); |
||||
|
|
||||
|
this.config.loadEntity = id => this.deviceService.getDeviceInfo(id.id); |
||||
|
this.config.saveEntity = device => { |
||||
|
return this.deviceService.saveDevice(device).pipe( |
||||
|
tap(() => { |
||||
|
this.broadcast.broadcast('deviceSaved'); |
||||
|
}), |
||||
|
mergeMap((savedDevice) => this.deviceService.getDeviceInfo(savedDevice.id.id) |
||||
|
)); |
||||
|
}; |
||||
|
this.config.onEntityAction = action => this.onDeviceAction(action); |
||||
|
|
||||
|
this.config.headerComponent = DeviceTableHeaderComponent; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
resolve(route: ActivatedRouteSnapshot): Observable<EntityTableConfig<DeviceInfo>> { |
||||
|
const routeParams = route.params; |
||||
|
this.config.componentsData = { |
||||
|
deviceScope: route.data.devicesType, |
||||
|
deviceType: '' |
||||
|
}; |
||||
|
this.customerId = routeParams.customerId; |
||||
|
return this.store.pipe(select(selectAuthUser), take(1)).pipe( |
||||
|
tap((authUser) => { |
||||
|
if (authUser.authority === Authority.CUSTOMER_USER) { |
||||
|
this.config.componentsData.deviceScope = 'customer_user'; |
||||
|
this.customerId = authUser.customerId; |
||||
|
} |
||||
|
}), |
||||
|
mergeMap(() => |
||||
|
this.customerId ? this.customerService.getCustomer(this.customerId) : of(null as Customer) |
||||
|
), |
||||
|
map((parentCustomer) => { |
||||
|
if (parentCustomer) { |
||||
|
if (parentCustomer.additionalInfo && parentCustomer.additionalInfo.isPublic) { |
||||
|
this.config.tableTitle = this.translate.instant('customer.public-devices'); |
||||
|
} else { |
||||
|
this.config.tableTitle = parentCustomer.title + ': ' + this.translate.instant('device.devices'); |
||||
|
} |
||||
|
} else { |
||||
|
this.config.tableTitle = this.translate.instant('device.devices'); |
||||
|
} |
||||
|
this.config.columns = this.configureColumns(this.config.componentsData.deviceScope); |
||||
|
this.configureEntityFunctions(this.config.componentsData.deviceScope); |
||||
|
this.config.cellActionDescriptors = this.configureCellActions(this.config.componentsData.deviceScope); |
||||
|
return this.config; |
||||
|
}) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
configureColumns(deviceScope: string): Array<EntityTableColumn<Device | DeviceInfo>> { |
||||
|
const columns: Array<EntityTableColumn<Device | DeviceInfo>> = [ |
||||
|
new DateEntityTableColumn<DeviceInfo>('createdTime', 'device.created-time', this.datePipe, '150px'), |
||||
|
new EntityTableColumn<DeviceInfo>('name', 'device.name'), |
||||
|
new EntityTableColumn<DeviceInfo>('type', 'device.device-type'), |
||||
|
new EntityTableColumn<DeviceInfo>('label', 'device.label') |
||||
|
]; |
||||
|
if (deviceScope === 'tenant') { |
||||
|
columns.push( |
||||
|
new EntityTableColumn<DeviceInfo>('customerTitle', 'customer.customer'), |
||||
|
new EntityTableColumn<DeviceInfo>('customerIsPublic', 'device.public', '60px', |
||||
|
entity => { |
||||
|
return checkBoxCell(entity.customerIsPublic); |
||||
|
}, () => ({}), false), |
||||
|
); |
||||
|
} |
||||
|
columns.push( |
||||
|
new EntityTableColumn<DeviceInfo>('gateway', 'device.is-gateway', '60px', |
||||
|
entity => { |
||||
|
return checkBoxCell(entity.additionalInfo && entity.additionalInfo.gateway); |
||||
|
}, () => ({}), false) |
||||
|
); |
||||
|
return columns; |
||||
|
} |
||||
|
|
||||
|
configureEntityFunctions(deviceScope: string): void { |
||||
|
if (deviceScope === 'tenant') { |
||||
|
this.config.entitiesFetchFunction = pageLink => this.deviceService.getTenantDeviceInfos(pageLink, this.config.componentsData.deviceType); |
||||
|
this.config.deleteEntity = id => this.deviceService.deleteDevice(id.id); |
||||
|
} else { |
||||
|
this.config.entitiesFetchFunction = pageLink => this.deviceService.getCustomerDeviceInfos(this.customerId, pageLink, this.config.componentsData.deviceType); |
||||
|
this.config.deleteEntity = id => this.deviceService.unassignDeviceFromCustomer(id.id); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
configureCellActions(deviceScope: string): Array<CellActionDescriptor<Device | DeviceInfo>> { |
||||
|
const actions: Array<CellActionDescriptor<Device | DeviceInfo>> = []; |
||||
|
if (deviceScope === 'tenant') { |
||||
|
actions.push( |
||||
|
{ |
||||
|
name: this.translate.instant('device.make-public'), |
||||
|
icon: 'share', |
||||
|
isEnabled: (entity) => (!entity.customerId || entity.customerId.id === NULL_UUID), |
||||
|
onAction: ($event, entity) => this.makePublic($event, entity) |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
return actions; |
||||
|
} |
||||
|
|
||||
|
makePublic($event: Event, device: Device) { |
||||
|
if ($event) { |
||||
|
$event.stopPropagation(); |
||||
|
} |
||||
|
// TODO:
|
||||
|
} |
||||
|
|
||||
|
onDeviceAction(action: EntityAction<Device | DeviceInfo>): boolean { |
||||
|
switch (action.action) { |
||||
|
case 'makePublic': |
||||
|
this.makePublic(action.event, action.entity); |
||||
|
return true; |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2019 The Thingsboard Authors |
||||
|
|
||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
you may not use this file except in compliance with the License. |
||||
|
You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
|
||||
|
--> |
||||
|
<mat-form-field [formGroup]="subTypeFormGroup" class="mat-block"> |
||||
|
<mat-label>{{ entitySubtypeText | translate }}</mat-label> |
||||
|
<input matInput type="text" placeholder="{{ selectEntitySubtypeText | translate }}" |
||||
|
#subTypeInput |
||||
|
formControlName="subType" |
||||
|
[required]="required" |
||||
|
[matAutocomplete]="subTypeAutocomplete"> |
||||
|
<button *ngIf="subTypeFormGroup.get('subType').value && !disabled" |
||||
|
type="button" |
||||
|
matSuffix mat-button mat-icon-button aria-label="Clear" |
||||
|
(click)="clear()"> |
||||
|
<mat-icon class="material-icons">close</mat-icon> |
||||
|
</button> |
||||
|
<mat-autocomplete #subTypeAutocomplete="matAutocomplete" [displayWith]="displaySubTypeFn"> |
||||
|
<mat-option *ngFor="let subType of filteredSubTypes | async" [value]="subType"> |
||||
|
<span [innerHTML]="subType.type | highlight:searchText"></span> |
||||
|
</mat-option> |
||||
|
</mat-autocomplete> |
||||
|
<mat-error *ngIf="subTypeFormGroup.get('subType').hasError('required')"> |
||||
|
{{ entitySubtypeRequiredText | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
@ -0,0 +1,228 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2019 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 {AfterViewInit, Component, ElementRef, forwardRef, Input, OnInit, ViewChild, OnDestroy} from '@angular/core'; |
||||
|
import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR} from '@angular/forms'; |
||||
|
import {Observable, of, throwError, Subscription} from 'rxjs'; |
||||
|
import {PageLink} from '@shared/models/page/page-link'; |
||||
|
import {Direction} from '@shared/models/page/sort-order'; |
||||
|
import {filter, map, mergeMap, publishReplay, refCount, startWith, tap, publish} from 'rxjs/operators'; |
||||
|
import {PageData, emptyPageData} from '@shared/models/page/page-data'; |
||||
|
import {DashboardInfo} from '@app/shared/models/dashboard.models'; |
||||
|
import {DashboardId} from '@app/shared/models/id/dashboard-id'; |
||||
|
import {DashboardService} from '@core/http/dashboard.service'; |
||||
|
import {Store} from '@ngrx/store'; |
||||
|
import {AppState} from '@app/core/core.state'; |
||||
|
import {getCurrentAuthUser} from '@app/core/auth/auth.selectors'; |
||||
|
import {Authority} from '@shared/models/authority.enum'; |
||||
|
import {TranslateService} from '@ngx-translate/core'; |
||||
|
import {DeviceService} from '@core/http/device.service'; |
||||
|
import {EntitySubtype, EntityType} from '@app/shared/models/entity-type.models'; |
||||
|
import {BroadcastService} from '@app/core/services/broadcast.service'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-entity-subtype-autocomplete', |
||||
|
templateUrl: './entity-subtype-autocomplete.component.html', |
||||
|
styleUrls: [], |
||||
|
providers: [{ |
||||
|
provide: NG_VALUE_ACCESSOR, |
||||
|
useExisting: forwardRef(() => EntitySubTypeAutocompleteComponent), |
||||
|
multi: true |
||||
|
}] |
||||
|
}) |
||||
|
export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor, OnInit, AfterViewInit, OnDestroy { |
||||
|
|
||||
|
subTypeFormGroup: FormGroup; |
||||
|
|
||||
|
modelValue: string | null; |
||||
|
|
||||
|
@Input() |
||||
|
entityType: EntityType; |
||||
|
|
||||
|
@Input() |
||||
|
required: boolean; |
||||
|
|
||||
|
@Input() |
||||
|
disabled: boolean; |
||||
|
|
||||
|
@ViewChild('subTypeInput', {static: true}) subTypeInput: ElementRef; |
||||
|
|
||||
|
selectEntitySubtypeText: string; |
||||
|
entitySubtypeText: string; |
||||
|
entitySubtypeRequiredText: string; |
||||
|
|
||||
|
filteredSubTypes: Observable<Array<EntitySubtype>>; |
||||
|
|
||||
|
subTypes: Observable<Array<EntitySubtype>>; |
||||
|
|
||||
|
private broadcastSubscription: Subscription; |
||||
|
|
||||
|
private searchText = ''; |
||||
|
|
||||
|
private propagateChange = (v: any) => { }; |
||||
|
|
||||
|
constructor(private store: Store<AppState>, |
||||
|
private broadcast: BroadcastService, |
||||
|
public translate: TranslateService, |
||||
|
private deviceService: DeviceService, |
||||
|
private fb: FormBuilder) { |
||||
|
this.subTypeFormGroup = this.fb.group({ |
||||
|
subType: [null] |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
registerOnChange(fn: any): void { |
||||
|
this.propagateChange = fn; |
||||
|
} |
||||
|
|
||||
|
registerOnTouched(fn: any): void { |
||||
|
} |
||||
|
|
||||
|
ngOnInit() { |
||||
|
|
||||
|
switch (this.entityType) { |
||||
|
case EntityType.ASSET: |
||||
|
this.selectEntitySubtypeText = 'asset.select-asset-type'; |
||||
|
this.entitySubtypeText = 'asset.asset-type'; |
||||
|
this.entitySubtypeRequiredText = 'asset.asset-type-required'; |
||||
|
this.broadcastSubscription = this.broadcast.on('assetSaved', () => { |
||||
|
this.subTypes = null; |
||||
|
}); |
||||
|
break; |
||||
|
case EntityType.DEVICE: |
||||
|
this.selectEntitySubtypeText = 'device.select-device-type'; |
||||
|
this.entitySubtypeText = 'device.device-type'; |
||||
|
this.entitySubtypeRequiredText = 'device.device-type-required'; |
||||
|
this.broadcastSubscription = this.broadcast.on('deviceSaved', () => { |
||||
|
this.subTypes = null; |
||||
|
}); |
||||
|
break; |
||||
|
case EntityType.ENTITY_VIEW: |
||||
|
this.selectEntitySubtypeText = 'entity-view.select-entity-view-type'; |
||||
|
this.entitySubtypeText = 'entity-view.entity-view-type'; |
||||
|
this.entitySubtypeRequiredText = 'entity-view.entity-view-type-required'; |
||||
|
this.broadcastSubscription = this.broadcast.on('entityViewSaved', () => { |
||||
|
this.subTypes = null; |
||||
|
}); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
this.filteredSubTypes = this.subTypeFormGroup.get('subType').valueChanges |
||||
|
.pipe( |
||||
|
tap(value => { |
||||
|
let modelValue; |
||||
|
if (!value) { |
||||
|
modelValue = null; |
||||
|
} else if (typeof value === 'string') { |
||||
|
modelValue = value; |
||||
|
} else { |
||||
|
modelValue = value.type; |
||||
|
} |
||||
|
this.updateView(modelValue); |
||||
|
}), |
||||
|
startWith<string | EntitySubtype>(''), |
||||
|
map(value => value ? (typeof value === 'string' ? value : value.type) : ''), |
||||
|
mergeMap(type => this.fetchSubTypes(type) ) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
ngAfterViewInit(): void { |
||||
|
} |
||||
|
|
||||
|
ngOnDestroy(): void { |
||||
|
if (this.broadcastSubscription) { |
||||
|
this.broadcastSubscription.unsubscribe(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
setDisabledState(isDisabled: boolean): void { |
||||
|
this.disabled = isDisabled; |
||||
|
} |
||||
|
|
||||
|
writeValue(value: string | null): void { |
||||
|
this.searchText = ''; |
||||
|
if (value != null) { |
||||
|
this.modelValue = value; |
||||
|
this.fetchSubTypes(value, true).subscribe( |
||||
|
(subTypes) => { |
||||
|
const subType = subTypes && subTypes.length === 1 ? subTypes[0] : null; |
||||
|
this.subTypeFormGroup.get('subType').patchValue(subType, {emitEvent: true}); |
||||
|
} |
||||
|
); |
||||
|
} else { |
||||
|
this.modelValue = null; |
||||
|
this.subTypeFormGroup.get('subType').patchValue(null, {emitEvent: true}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
updateView(value: string | null) { |
||||
|
if (this.modelValue !== value) { |
||||
|
this.modelValue = value; |
||||
|
this.propagateChange(this.modelValue); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
displaySubTypeFn(subType?: EntitySubtype): string | undefined { |
||||
|
return subType ? subType.type : undefined; |
||||
|
} |
||||
|
|
||||
|
fetchSubTypes(searchText?: string, strictMatch: boolean = false): Observable<Array<EntitySubtype>> { |
||||
|
this.searchText = searchText; |
||||
|
return this.getSubTypes().pipe( |
||||
|
map(subTypes => subTypes.filter( subType => { |
||||
|
if (strictMatch) { |
||||
|
return searchText ? subType.type === searchText : false; |
||||
|
} else { |
||||
|
return searchText ? subType.type.toUpperCase().startsWith(searchText.toUpperCase()) : true; |
||||
|
} |
||||
|
})) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
getSubTypes(): Observable<Array<EntitySubtype>> { |
||||
|
if (!this.subTypes) { |
||||
|
switch (this.entityType) { |
||||
|
case EntityType.ASSET: |
||||
|
// TODO:
|
||||
|
break; |
||||
|
case EntityType.DEVICE: |
||||
|
this.subTypes = this.deviceService.getDeviceTypes(false, true); |
||||
|
break; |
||||
|
case EntityType.ENTITY_VIEW: |
||||
|
// TODO:
|
||||
|
break; |
||||
|
} |
||||
|
if (this.subTypes) { |
||||
|
this.subTypes = this.subTypes.pipe( |
||||
|
publishReplay(1), |
||||
|
refCount() |
||||
|
); |
||||
|
} else { |
||||
|
return throwError(null); |
||||
|
} |
||||
|
} |
||||
|
return this.subTypes; |
||||
|
} |
||||
|
|
||||
|
clear() { |
||||
|
this.subTypeFormGroup.get('subType').patchValue(null, {emitEvent: true}); |
||||
|
setTimeout(() => { |
||||
|
this.subTypeInput.nativeElement.blur(); |
||||
|
this.subTypeInput.nativeElement.focus(); |
||||
|
}, 0); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2019 The Thingsboard Authors |
||||
|
|
||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
you may not use this file except in compliance with the License. |
||||
|
You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
|
||||
|
--> |
||||
|
<mat-form-field [formGroup]="subTypeFormGroup" class="mat-block"> |
||||
|
<mat-label *ngIf="showLabel">{{ entitySubtypeTitle | translate }}</mat-label> |
||||
|
<mat-select class="tb-entity-subtype-select" matInput formControlName="subType"> |
||||
|
<mat-option *ngFor="let subType of subTypesOptions | async" [value]="subType"> |
||||
|
{{ displaySubTypeFn(subType) }} |
||||
|
</mat-option> |
||||
|
</mat-select> |
||||
|
<mat-error *ngIf="subTypeFormGroup.get('subType').hasError('required')"> |
||||
|
{{ entitySubtypeRequiredText | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
@ -0,0 +1,20 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2019 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 { |
||||
|
mat-select.tb-entity-subtype-select { |
||||
|
min-width: 200px; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,238 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2019 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 {AfterViewInit, Component, ElementRef, forwardRef, Input, OnInit, ViewChild, OnDestroy} from '@angular/core'; |
||||
|
import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR} from '@angular/forms'; |
||||
|
import {Observable, of, throwError, Subscription, Subject} from 'rxjs'; |
||||
|
import {PageLink} from '@shared/models/page/page-link'; |
||||
|
import {Direction} from '@shared/models/page/sort-order'; |
||||
|
import {filter, map, mergeMap, publishReplay, refCount, startWith, tap, publish} from 'rxjs/operators'; |
||||
|
import {PageData, emptyPageData} from '@shared/models/page/page-data'; |
||||
|
import {DashboardInfo} from '@app/shared/models/dashboard.models'; |
||||
|
import {DashboardId} from '@app/shared/models/id/dashboard-id'; |
||||
|
import {DashboardService} from '@core/http/dashboard.service'; |
||||
|
import {Store} from '@ngrx/store'; |
||||
|
import {AppState} from '@app/core/core.state'; |
||||
|
import {getCurrentAuthUser} from '@app/core/auth/auth.selectors'; |
||||
|
import {Authority} from '@shared/models/authority.enum'; |
||||
|
import {TranslateService} from '@ngx-translate/core'; |
||||
|
import {DeviceService} from '@core/http/device.service'; |
||||
|
import {EntitySubtype, EntityType} from '@app/shared/models/entity-type.models'; |
||||
|
import {BroadcastService} from '@app/core/services/broadcast.service'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-entity-subtype-select', |
||||
|
templateUrl: './entity-subtype-select.component.html', |
||||
|
styleUrls: ['./entity-subtype-select.component.scss'], |
||||
|
providers: [{ |
||||
|
provide: NG_VALUE_ACCESSOR, |
||||
|
useExisting: forwardRef(() => EntitySubTypeSelectComponent), |
||||
|
multi: true |
||||
|
}] |
||||
|
}) |
||||
|
export class EntitySubTypeSelectComponent implements ControlValueAccessor, OnInit, AfterViewInit, OnDestroy { |
||||
|
|
||||
|
subTypeFormGroup: FormGroup; |
||||
|
|
||||
|
modelValue: string | null; |
||||
|
|
||||
|
@Input() |
||||
|
entityType: EntityType; |
||||
|
|
||||
|
@Input() |
||||
|
showLabel: boolean; |
||||
|
|
||||
|
@Input() |
||||
|
required: boolean; |
||||
|
|
||||
|
@Input() |
||||
|
disabled: boolean; |
||||
|
|
||||
|
@Input() |
||||
|
typeTranslatePrefix: string; |
||||
|
|
||||
|
@ViewChild('subTypeInput', {static: true}) subTypeInput: ElementRef; |
||||
|
|
||||
|
entitySubtypeTitle: string; |
||||
|
entitySubtypeRequiredText: string; |
||||
|
|
||||
|
subTypesOptions: Observable<Array<EntitySubtype | string>>; |
||||
|
|
||||
|
private subTypesOptionsSubject: Subject<string> = new Subject(); |
||||
|
|
||||
|
subTypes: Observable<Array<EntitySubtype | string>>; |
||||
|
|
||||
|
private broadcastSubscription: Subscription; |
||||
|
|
||||
|
private propagateChange = (v: any) => { }; |
||||
|
|
||||
|
constructor(private store: Store<AppState>, |
||||
|
private broadcast: BroadcastService, |
||||
|
public translate: TranslateService, |
||||
|
private deviceService: DeviceService, |
||||
|
private fb: FormBuilder) { |
||||
|
this.subTypeFormGroup = this.fb.group({ |
||||
|
subType: [null] |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
registerOnChange(fn: any): void { |
||||
|
this.propagateChange = fn; |
||||
|
} |
||||
|
|
||||
|
registerOnTouched(fn: any): void { |
||||
|
} |
||||
|
|
||||
|
ngOnInit() { |
||||
|
|
||||
|
switch (this.entityType) { |
||||
|
case EntityType.ASSET: |
||||
|
this.entitySubtypeTitle = 'asset.asset-type'; |
||||
|
this.entitySubtypeRequiredText = 'asset.asset-type-required'; |
||||
|
this.broadcastSubscription = this.broadcast.on('assetSaved', () => { |
||||
|
this.subTypes = null; |
||||
|
this.subTypesOptionsSubject.next(''); |
||||
|
}); |
||||
|
break; |
||||
|
case EntityType.DEVICE: |
||||
|
this.entitySubtypeTitle = 'device.device-type'; |
||||
|
this.entitySubtypeRequiredText = 'device.device-type-required'; |
||||
|
this.broadcastSubscription = this.broadcast.on('deviceSaved', () => { |
||||
|
this.subTypes = null; |
||||
|
this.subTypesOptionsSubject.next(''); |
||||
|
}); |
||||
|
break; |
||||
|
case EntityType.ENTITY_VIEW: |
||||
|
this.entitySubtypeTitle = 'entity-view.entity-view-type'; |
||||
|
this.entitySubtypeRequiredText = 'entity-view.entity-view-type-required'; |
||||
|
this.broadcastSubscription = this.broadcast.on('entityViewSaved', () => { |
||||
|
this.subTypes = null; |
||||
|
this.subTypesOptionsSubject.next(''); |
||||
|
}); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
this.subTypesOptions = this.subTypesOptionsSubject.asObservable().pipe( |
||||
|
startWith<string | EntitySubtype>(''), |
||||
|
mergeMap(() => this.getSubTypes()) |
||||
|
); |
||||
|
|
||||
|
this.subTypeFormGroup.get('subType').valueChanges.subscribe( |
||||
|
(value) => { |
||||
|
let modelValue; |
||||
|
if (!value || value === '') { |
||||
|
modelValue = ''; |
||||
|
} else { |
||||
|
modelValue = value.type; |
||||
|
} |
||||
|
this.updateView(modelValue); |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
ngAfterViewInit(): void { |
||||
|
} |
||||
|
|
||||
|
ngOnDestroy(): void { |
||||
|
if (this.broadcastSubscription) { |
||||
|
this.broadcastSubscription.unsubscribe(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
setDisabledState(isDisabled: boolean): void { |
||||
|
this.disabled = isDisabled; |
||||
|
} |
||||
|
|
||||
|
writeValue(value: string | null): void { |
||||
|
if (value != null && value !== '') { |
||||
|
this.modelValue = value; |
||||
|
this.findSubTypes(value).subscribe( |
||||
|
(subTypes) => { |
||||
|
const subType = subTypes && subTypes.length === 1 ? subTypes[0] : ''; |
||||
|
this.subTypeFormGroup.get('subType').patchValue(subType, {emitEvent: true}); |
||||
|
} |
||||
|
); |
||||
|
} else { |
||||
|
this.modelValue = ''; |
||||
|
this.subTypeFormGroup.get('subType').patchValue('', {emitEvent: true}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
updateView(value: string | null) { |
||||
|
if (this.modelValue !== value) { |
||||
|
this.modelValue = value; |
||||
|
this.propagateChange(this.modelValue); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
displaySubTypeFn(subType?: EntitySubtype | string): string | undefined { |
||||
|
if (subType && typeof subType !== 'string') { |
||||
|
if (this.typeTranslatePrefix) { |
||||
|
return this.translate.instant(this.typeTranslatePrefix + '.' + subType.type); |
||||
|
} else { |
||||
|
return subType.type; |
||||
|
} |
||||
|
} else { |
||||
|
return this.translate.instant('entity.all-subtypes'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
findSubTypes(searchText?: string): Observable<Array<EntitySubtype | string>> { |
||||
|
return this.getSubTypes().pipe( |
||||
|
map(subTypes => subTypes.filter( subType => { |
||||
|
return searchText ? (typeof subType === 'string' ? false : subType.type === searchText) : false; |
||||
|
})) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
getSubTypes(): Observable<Array<EntitySubtype | string>> { |
||||
|
if (!this.subTypes) { |
||||
|
switch (this.entityType) { |
||||
|
case EntityType.ASSET: |
||||
|
// TODO:
|
||||
|
break; |
||||
|
case EntityType.DEVICE: |
||||
|
this.subTypes = this.deviceService.getDeviceTypes(false, true); |
||||
|
break; |
||||
|
case EntityType.ENTITY_VIEW: |
||||
|
// TODO:
|
||||
|
break; |
||||
|
} |
||||
|
if (this.subTypes) { |
||||
|
this.subTypes = this.subTypes.pipe( |
||||
|
map((allSubtypes) => { |
||||
|
allSubtypes.unshift(''); |
||||
|
return allSubtypes; |
||||
|
}), |
||||
|
publishReplay(1), |
||||
|
refCount() |
||||
|
); |
||||
|
} else { |
||||
|
return throwError(null); |
||||
|
} |
||||
|
} |
||||
|
return this.subTypes; |
||||
|
} |
||||
|
|
||||
|
clear() { |
||||
|
this.subTypeFormGroup.get('subType').patchValue(null, {emitEvent: true}); |
||||
|
setTimeout(() => { |
||||
|
this.subTypeInput.nativeElement.blur(); |
||||
|
this.subTypeInput.nativeElement.focus(); |
||||
|
}, 0); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2019 The Thingsboard Authors
|
||||
|
///
|
||||
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
/// you may not use this file except in compliance with the License.
|
||||
|
/// You may obtain a copy of the License at
|
||||
|
///
|
||||
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
///
|
||||
|
/// Unless required by applicable law or agreed to in writing, software
|
||||
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
/// See the License for the specific language governing permissions and
|
||||
|
/// limitations under the License.
|
||||
|
///
|
||||
|
|
||||
|
import {BaseData} from '@shared/models/base-data'; |
||||
|
import {DeviceId} from './id/device-id'; |
||||
|
import {TenantId} from '@shared/models/id/tenant-id'; |
||||
|
import {CustomerId} from '@shared/models/id/customer-id'; |
||||
|
|
||||
|
export interface Device extends BaseData<DeviceId> { |
||||
|
tenantId: TenantId; |
||||
|
customerId: CustomerId; |
||||
|
name: string; |
||||
|
type: string; |
||||
|
label: string; |
||||
|
additionalInfo?: any; |
||||
|
} |
||||
|
|
||||
|
export interface DeviceInfo extends Device { |
||||
|
customerTitle: string; |
||||
|
customerIsPublic: boolean; |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2019 The Thingsboard Authors
|
||||
|
///
|
||||
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
/// you may not use this file except in compliance with the License.
|
||||
|
/// You may obtain a copy of the License at
|
||||
|
///
|
||||
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
///
|
||||
|
/// Unless required by applicable law or agreed to in writing, software
|
||||
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
/// See the License for the specific language governing permissions and
|
||||
|
/// limitations under the License.
|
||||
|
///
|
||||
|
|
||||
|
import { EntityId } from './entity-id'; |
||||
|
import { EntityType } from '@shared/models/entity-type.models'; |
||||
|
|
||||
|
export class DeviceId implements EntityId { |
||||
|
entityType = EntityType.DEVICE; |
||||
|
id: string; |
||||
|
constructor(id: string) { |
||||
|
this.id = id; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue