72 changed files with 4941 additions and 713 deletions
@ -0,0 +1,46 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.device; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.relation.EntityRelation; |
||||
|
import org.thingsboard.server.dao.relation.RelationsSearchParameters; |
||||
|
import org.thingsboard.server.dao.relation.EntityRelationsQuery; |
||||
|
import org.thingsboard.server.dao.relation.EntityTypeFilter; |
||||
|
|
||||
|
import javax.annotation.Nullable; |
||||
|
import java.util.Collections; |
||||
|
import java.util.List; |
||||
|
|
||||
|
@Data |
||||
|
public class DeviceSearchQuery { |
||||
|
|
||||
|
private RelationsSearchParameters parameters; |
||||
|
@Nullable |
||||
|
private String relationType; |
||||
|
@Nullable |
||||
|
private List<String> deviceTypes; |
||||
|
|
||||
|
public EntityRelationsQuery toEntitySearchQuery() { |
||||
|
EntityRelationsQuery query = new EntityRelationsQuery(); |
||||
|
query.setParameters(parameters); |
||||
|
query.setFilters( |
||||
|
Collections.singletonList(new EntityTypeFilter(relationType == null ? EntityRelation.CONTAINS_TYPE : relationType, |
||||
|
Collections.singletonList(EntityType.DEVICE)))); |
||||
|
return query; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,283 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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.service; |
||||
|
|
||||
|
import com.datastax.driver.core.utils.UUIDs; |
||||
|
import com.google.common.util.concurrent.ListenableFuture; |
||||
|
import org.junit.After; |
||||
|
import org.junit.Assert; |
||||
|
import org.junit.Before; |
||||
|
import org.junit.Test; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.asset.Asset; |
||||
|
import org.thingsboard.server.common.data.id.AssetId; |
||||
|
import org.thingsboard.server.common.data.id.DeviceId; |
||||
|
import org.thingsboard.server.common.data.relation.EntityRelation; |
||||
|
import org.thingsboard.server.dao.exception.DataValidationException; |
||||
|
import org.thingsboard.server.dao.relation.EntityRelationsQuery; |
||||
|
import org.thingsboard.server.dao.relation.EntitySearchDirection; |
||||
|
import org.thingsboard.server.dao.relation.EntityTypeFilter; |
||||
|
import org.thingsboard.server.dao.relation.RelationsSearchParameters; |
||||
|
|
||||
|
import java.util.Collections; |
||||
|
import java.util.List; |
||||
|
import java.util.concurrent.ExecutionException; |
||||
|
|
||||
|
public class RelationServiceImplTest extends AbstractServiceTest { |
||||
|
|
||||
|
@Before |
||||
|
public void before() { |
||||
|
} |
||||
|
|
||||
|
@After |
||||
|
public void after() { |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testSaveRelation() throws ExecutionException, InterruptedException { |
||||
|
AssetId parentId = new AssetId(UUIDs.timeBased()); |
||||
|
AssetId childId = new AssetId(UUIDs.timeBased()); |
||||
|
|
||||
|
EntityRelation relation = new EntityRelation(parentId, childId, EntityRelation.CONTAINS_TYPE); |
||||
|
|
||||
|
Assert.assertTrue(saveRelation(relation)); |
||||
|
|
||||
|
Assert.assertTrue(relationService.checkRelation(parentId, childId, EntityRelation.CONTAINS_TYPE).get()); |
||||
|
|
||||
|
Assert.assertFalse(relationService.checkRelation(parentId, childId, "NOT_EXISTING_TYPE").get()); |
||||
|
|
||||
|
Assert.assertFalse(relationService.checkRelation(childId, parentId, EntityRelation.CONTAINS_TYPE).get()); |
||||
|
|
||||
|
Assert.assertFalse(relationService.checkRelation(childId, parentId, "NOT_EXISTING_TYPE").get()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testDeleteRelation() throws ExecutionException, InterruptedException { |
||||
|
AssetId parentId = new AssetId(UUIDs.timeBased()); |
||||
|
AssetId childId = new AssetId(UUIDs.timeBased()); |
||||
|
AssetId subChildId = new AssetId(UUIDs.timeBased()); |
||||
|
|
||||
|
EntityRelation relationA = new EntityRelation(parentId, childId, EntityRelation.CONTAINS_TYPE); |
||||
|
EntityRelation relationB = new EntityRelation(childId, subChildId, EntityRelation.CONTAINS_TYPE); |
||||
|
|
||||
|
saveRelation(relationA); |
||||
|
saveRelation(relationB); |
||||
|
|
||||
|
Assert.assertTrue(relationService.deleteRelation(relationA).get()); |
||||
|
|
||||
|
Assert.assertFalse(relationService.checkRelation(parentId, childId, EntityRelation.CONTAINS_TYPE).get()); |
||||
|
|
||||
|
Assert.assertTrue(relationService.checkRelation(childId, subChildId, EntityRelation.CONTAINS_TYPE).get()); |
||||
|
|
||||
|
Assert.assertTrue(relationService.deleteRelation(childId, subChildId, EntityRelation.CONTAINS_TYPE).get()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testDeleteEntityRelations() throws ExecutionException, InterruptedException { |
||||
|
AssetId parentId = new AssetId(UUIDs.timeBased()); |
||||
|
AssetId childId = new AssetId(UUIDs.timeBased()); |
||||
|
AssetId subChildId = new AssetId(UUIDs.timeBased()); |
||||
|
|
||||
|
EntityRelation relationA = new EntityRelation(parentId, childId, EntityRelation.CONTAINS_TYPE); |
||||
|
EntityRelation relationB = new EntityRelation(childId, subChildId, EntityRelation.CONTAINS_TYPE); |
||||
|
|
||||
|
saveRelation(relationA); |
||||
|
saveRelation(relationB); |
||||
|
|
||||
|
Assert.assertTrue(relationService.deleteEntityRelations(childId).get()); |
||||
|
|
||||
|
Assert.assertFalse(relationService.checkRelation(parentId, childId, EntityRelation.CONTAINS_TYPE).get()); |
||||
|
|
||||
|
Assert.assertFalse(relationService.checkRelation(childId, subChildId, EntityRelation.CONTAINS_TYPE).get()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testFindFrom() throws ExecutionException, InterruptedException { |
||||
|
AssetId parentA = new AssetId(UUIDs.timeBased()); |
||||
|
AssetId parentB = new AssetId(UUIDs.timeBased()); |
||||
|
AssetId childA = new AssetId(UUIDs.timeBased()); |
||||
|
AssetId childB = new AssetId(UUIDs.timeBased()); |
||||
|
|
||||
|
EntityRelation relationA1 = new EntityRelation(parentA, childA, EntityRelation.CONTAINS_TYPE); |
||||
|
EntityRelation relationA2 = new EntityRelation(parentA, childB, EntityRelation.CONTAINS_TYPE); |
||||
|
|
||||
|
EntityRelation relationB1 = new EntityRelation(parentB, childA, EntityRelation.MANAGES_TYPE); |
||||
|
EntityRelation relationB2 = new EntityRelation(parentB, childB, EntityRelation.MANAGES_TYPE); |
||||
|
|
||||
|
saveRelation(relationA1); |
||||
|
saveRelation(relationA2); |
||||
|
|
||||
|
saveRelation(relationB1); |
||||
|
saveRelation(relationB2); |
||||
|
|
||||
|
List<EntityRelation> relations = relationService.findByFrom(parentA).get(); |
||||
|
Assert.assertEquals(2, relations.size()); |
||||
|
for (EntityRelation relation : relations) { |
||||
|
Assert.assertEquals(EntityRelation.CONTAINS_TYPE, relation.getType()); |
||||
|
Assert.assertEquals(parentA, relation.getFrom()); |
||||
|
Assert.assertTrue(childA.equals(relation.getTo()) || childB.equals(relation.getTo())); |
||||
|
} |
||||
|
|
||||
|
relations = relationService.findByFromAndType(parentA, EntityRelation.CONTAINS_TYPE).get(); |
||||
|
Assert.assertEquals(2, relations.size()); |
||||
|
|
||||
|
relations = relationService.findByFromAndType(parentA, EntityRelation.MANAGES_TYPE).get(); |
||||
|
Assert.assertEquals(0, relations.size()); |
||||
|
|
||||
|
relations = relationService.findByFrom(parentB).get(); |
||||
|
Assert.assertEquals(2, relations.size()); |
||||
|
for (EntityRelation relation : relations) { |
||||
|
Assert.assertEquals(EntityRelation.MANAGES_TYPE, relation.getType()); |
||||
|
Assert.assertEquals(parentB, relation.getFrom()); |
||||
|
Assert.assertTrue(childA.equals(relation.getTo()) || childB.equals(relation.getTo())); |
||||
|
} |
||||
|
|
||||
|
relations = relationService.findByFromAndType(parentB, EntityRelation.CONTAINS_TYPE).get(); |
||||
|
Assert.assertEquals(0, relations.size()); |
||||
|
|
||||
|
relations = relationService.findByFromAndType(parentB, EntityRelation.CONTAINS_TYPE).get(); |
||||
|
Assert.assertEquals(0, relations.size()); |
||||
|
} |
||||
|
|
||||
|
private Boolean saveRelation(EntityRelation relationA1) throws ExecutionException, InterruptedException { |
||||
|
return relationService.saveRelation(relationA1).get(); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testFindTo() throws ExecutionException, InterruptedException { |
||||
|
AssetId parentA = new AssetId(UUIDs.timeBased()); |
||||
|
AssetId parentB = new AssetId(UUIDs.timeBased()); |
||||
|
AssetId childA = new AssetId(UUIDs.timeBased()); |
||||
|
AssetId childB = new AssetId(UUIDs.timeBased()); |
||||
|
|
||||
|
EntityRelation relationA1 = new EntityRelation(parentA, childA, EntityRelation.CONTAINS_TYPE); |
||||
|
EntityRelation relationA2 = new EntityRelation(parentA, childB, EntityRelation.CONTAINS_TYPE); |
||||
|
|
||||
|
EntityRelation relationB1 = new EntityRelation(parentB, childA, EntityRelation.MANAGES_TYPE); |
||||
|
EntityRelation relationB2 = new EntityRelation(parentB, childB, EntityRelation.MANAGES_TYPE); |
||||
|
|
||||
|
saveRelation(relationA1); |
||||
|
saveRelation(relationA2); |
||||
|
|
||||
|
saveRelation(relationB1); |
||||
|
saveRelation(relationB2); |
||||
|
|
||||
|
// Data propagation to views is async
|
||||
|
Thread.sleep(3000); |
||||
|
|
||||
|
List<EntityRelation> relations = relationService.findByTo(childA).get(); |
||||
|
Assert.assertEquals(2, relations.size()); |
||||
|
for (EntityRelation relation : relations) { |
||||
|
Assert.assertEquals(childA, relation.getTo()); |
||||
|
Assert.assertTrue(parentA.equals(relation.getFrom()) || parentB.equals(relation.getFrom())); |
||||
|
} |
||||
|
|
||||
|
relations = relationService.findByToAndType(childA, EntityRelation.CONTAINS_TYPE).get(); |
||||
|
Assert.assertEquals(1, relations.size()); |
||||
|
|
||||
|
relations = relationService.findByToAndType(childB, EntityRelation.MANAGES_TYPE).get(); |
||||
|
Assert.assertEquals(1, relations.size()); |
||||
|
|
||||
|
relations = relationService.findByToAndType(parentA, EntityRelation.MANAGES_TYPE).get(); |
||||
|
Assert.assertEquals(0, relations.size()); |
||||
|
|
||||
|
relations = relationService.findByToAndType(parentB, EntityRelation.MANAGES_TYPE).get(); |
||||
|
Assert.assertEquals(0, relations.size()); |
||||
|
|
||||
|
relations = relationService.findByTo(childB).get(); |
||||
|
Assert.assertEquals(2, relations.size()); |
||||
|
for (EntityRelation relation : relations) { |
||||
|
Assert.assertEquals(childB, relation.getTo()); |
||||
|
Assert.assertTrue(parentA.equals(relation.getFrom()) || parentB.equals(relation.getFrom())); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testCyclicRecursiveRelation() throws ExecutionException, InterruptedException { |
||||
|
// A -> B -> C -> A
|
||||
|
AssetId assetA = new AssetId(UUIDs.timeBased()); |
||||
|
AssetId assetB = new AssetId(UUIDs.timeBased()); |
||||
|
AssetId assetC = new AssetId(UUIDs.timeBased()); |
||||
|
|
||||
|
EntityRelation relationA = new EntityRelation(assetA, assetB, EntityRelation.CONTAINS_TYPE); |
||||
|
EntityRelation relationB = new EntityRelation(assetB, assetC, EntityRelation.CONTAINS_TYPE); |
||||
|
EntityRelation relationC = new EntityRelation(assetC, assetA, EntityRelation.CONTAINS_TYPE); |
||||
|
|
||||
|
saveRelation(relationA); |
||||
|
saveRelation(relationB); |
||||
|
saveRelation(relationC); |
||||
|
|
||||
|
EntityRelationsQuery query = new EntityRelationsQuery(); |
||||
|
query.setParameters(new RelationsSearchParameters(assetA, EntitySearchDirection.FROM, -1)); |
||||
|
query.setFilters(Collections.singletonList(new EntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.singletonList(EntityType.ASSET)))); |
||||
|
List<EntityRelation> relations = relationService.findByQuery(query).get(); |
||||
|
Assert.assertEquals(3, relations.size()); |
||||
|
Assert.assertTrue(relations.contains(relationA)); |
||||
|
Assert.assertTrue(relations.contains(relationB)); |
||||
|
Assert.assertTrue(relations.contains(relationC)); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testRecursiveRelation() throws ExecutionException, InterruptedException { |
||||
|
// A -> B -> [C,D]
|
||||
|
AssetId assetA = new AssetId(UUIDs.timeBased()); |
||||
|
AssetId assetB = new AssetId(UUIDs.timeBased()); |
||||
|
AssetId assetC = new AssetId(UUIDs.timeBased()); |
||||
|
DeviceId deviceD = new DeviceId(UUIDs.timeBased()); |
||||
|
|
||||
|
EntityRelation relationAB = new EntityRelation(assetA, assetB, EntityRelation.CONTAINS_TYPE); |
||||
|
EntityRelation relationBC = new EntityRelation(assetB, assetC, EntityRelation.CONTAINS_TYPE); |
||||
|
EntityRelation relationBD = new EntityRelation(assetB, deviceD, EntityRelation.CONTAINS_TYPE); |
||||
|
|
||||
|
|
||||
|
saveRelation(relationAB); |
||||
|
saveRelation(relationBC); |
||||
|
saveRelation(relationBD); |
||||
|
|
||||
|
EntityRelationsQuery query = new EntityRelationsQuery(); |
||||
|
query.setParameters(new RelationsSearchParameters(assetA, EntitySearchDirection.FROM, -1)); |
||||
|
query.setFilters(Collections.singletonList(new EntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.singletonList(EntityType.ASSET)))); |
||||
|
List<EntityRelation> relations = relationService.findByQuery(query).get(); |
||||
|
Assert.assertEquals(2, relations.size()); |
||||
|
Assert.assertTrue(relations.contains(relationAB)); |
||||
|
Assert.assertTrue(relations.contains(relationBC)); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
@Test(expected = DataValidationException.class) |
||||
|
public void testSaveRelationWithEmptyFrom() throws ExecutionException, InterruptedException { |
||||
|
EntityRelation relation = new EntityRelation(); |
||||
|
relation.setTo(new AssetId(UUIDs.timeBased())); |
||||
|
relation.setType(EntityRelation.CONTAINS_TYPE); |
||||
|
Assert.assertTrue(saveRelation(relation)); |
||||
|
} |
||||
|
|
||||
|
@Test(expected = DataValidationException.class) |
||||
|
public void testSaveRelationWithEmptyTo() throws ExecutionException, InterruptedException { |
||||
|
EntityRelation relation = new EntityRelation(); |
||||
|
relation.setFrom(new AssetId(UUIDs.timeBased())); |
||||
|
relation.setType(EntityRelation.CONTAINS_TYPE); |
||||
|
Assert.assertTrue(saveRelation(relation)); |
||||
|
} |
||||
|
|
||||
|
@Test(expected = DataValidationException.class) |
||||
|
public void testSaveRelationWithEmptyType() throws ExecutionException, InterruptedException { |
||||
|
EntityRelation relation = new EntityRelation(); |
||||
|
relation.setFrom(new AssetId(UUIDs.timeBased())); |
||||
|
relation.setTo(new AssetId(UUIDs.timeBased())); |
||||
|
Assert.assertTrue(saveRelation(relation)); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,128 @@ |
|||||
|
/* |
||||
|
* Copyright © 2016-2017 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 './related-entity-autocomplete.scss'; |
||||
|
|
||||
|
/* eslint-disable import/no-unresolved, import/default */ |
||||
|
|
||||
|
import relatedEntityAutocompleteTemplate from './related-entity-autocomplete.tpl.html'; |
||||
|
|
||||
|
/* eslint-enable import/no-unresolved, import/default */ |
||||
|
|
||||
|
|
||||
|
export default angular.module('thingsboard.directives.relatedEntityAutocomplete', []) |
||||
|
.directive('tbRelatedEntityAutocomplete', RelatedEntityAutocomplete) |
||||
|
.name; |
||||
|
|
||||
|
/*@ngInject*/ |
||||
|
function RelatedEntityAutocomplete($compile, $templateCache, $q, $filter, entityService) { |
||||
|
|
||||
|
var linker = function (scope, element, attrs, ngModelCtrl) { |
||||
|
var template = $templateCache.get(relatedEntityAutocompleteTemplate); |
||||
|
element.html(template); |
||||
|
|
||||
|
scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false; |
||||
|
scope.entity = null; |
||||
|
scope.entitySearchText = ''; |
||||
|
|
||||
|
scope.allEntities = null; |
||||
|
|
||||
|
scope.fetchEntities = function(searchText) { |
||||
|
var deferred = $q.defer(); |
||||
|
if (!scope.allEntities) { |
||||
|
entityService.getRelatedEntities(scope.rootEntityId, scope.entityType, scope.entitySubtypes, -1, []).then( |
||||
|
function success(entities) { |
||||
|
if (scope.excludeEntityId) { |
||||
|
var result = $filter('filter')(entities, {id: {id: scope.excludeEntityId.id} }, true); |
||||
|
result = $filter('filter')(result, {id: {entityType: scope.excludeEntityId.entityType} }, true); |
||||
|
if (result && result.length) { |
||||
|
var excludeEntity = result[0]; |
||||
|
var index = entities.indexOf(excludeEntity); |
||||
|
if (index > -1) { |
||||
|
entities.splice(index, 1); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
scope.allEntities = entities; |
||||
|
filterEntities(searchText, deferred); |
||||
|
}, |
||||
|
function fail() { |
||||
|
deferred.reject(); |
||||
|
} |
||||
|
); |
||||
|
} else { |
||||
|
filterEntities(searchText, deferred); |
||||
|
} |
||||
|
return deferred.promise; |
||||
|
} |
||||
|
|
||||
|
function filterEntities(searchText, deferred) { |
||||
|
var result = $filter('filter')(scope.allEntities, {name: searchText}); |
||||
|
deferred.resolve(result); |
||||
|
} |
||||
|
|
||||
|
scope.entitySearchTextChanged = function() { |
||||
|
} |
||||
|
|
||||
|
scope.updateView = function () { |
||||
|
if (!scope.disabled) { |
||||
|
ngModelCtrl.$setViewValue(scope.entity ? scope.entity.id : null); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
ngModelCtrl.$render = function () { |
||||
|
if (ngModelCtrl.$viewValue) { |
||||
|
entityService.getRelatedEntity(ngModelCtrl.$viewValue).then( |
||||
|
function success(entity) { |
||||
|
scope.entity = entity; |
||||
|
}, |
||||
|
function fail() { |
||||
|
scope.entity = null; |
||||
|
} |
||||
|
); |
||||
|
} else { |
||||
|
scope.entity = null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
scope.$watch('entity', function () { |
||||
|
scope.updateView(); |
||||
|
}); |
||||
|
|
||||
|
scope.$watch('disabled', function () { |
||||
|
scope.updateView(); |
||||
|
}); |
||||
|
|
||||
|
$compile(element.contents())(scope); |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
restrict: "E", |
||||
|
require: "^ngModel", |
||||
|
link: linker, |
||||
|
scope: { |
||||
|
rootEntityId: '=', |
||||
|
entityType: '=', |
||||
|
entitySubtypes: '=', |
||||
|
excludeEntityId: '=?', |
||||
|
theForm: '=?', |
||||
|
tbRequired: '=?', |
||||
|
disabled:'=ngDisabled', |
||||
|
placeholderText: '@', |
||||
|
notFoundText: '@', |
||||
|
requiredText: '@' |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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-related-entity-autocomplete { |
||||
|
.tb-not-found { |
||||
|
display: block; |
||||
|
line-height: 1.5; |
||||
|
height: 48px; |
||||
|
} |
||||
|
.tb-entity-item { |
||||
|
display: block; |
||||
|
height: 48px; |
||||
|
} |
||||
|
li { |
||||
|
height: auto !important; |
||||
|
white-space: normal !important; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2017 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. |
||||
|
|
||||
|
--> |
||||
|
<md-autocomplete ng-required="tbRequired" |
||||
|
ng-disabled="disabled" |
||||
|
md-input-name="entity" |
||||
|
ng-model="entity" |
||||
|
md-selected-item="entity" |
||||
|
md-search-text="entitySearchText" |
||||
|
md-search-text-change="entitySearchTextChanged()" |
||||
|
md-items="item in fetchEntities(entitySearchText)" |
||||
|
md-item-text="item.name" |
||||
|
md-min-length="0" |
||||
|
md-floating-label="{{ placeholderText | translate }}" |
||||
|
md-menu-class="tb-related-entity-autocomplete"> |
||||
|
<md-item-template> |
||||
|
<div class="tb-entity-item"> |
||||
|
<span md-highlight-text="entitySearchText" md-highlight-flags="^i">{{item.name}}</span> |
||||
|
</div> |
||||
|
</md-item-template> |
||||
|
<md-not-found> |
||||
|
<div class="tb-not-found"> |
||||
|
<span>{{ notFoundText | translate:{entity: entitySearchText} }}</span> |
||||
|
</div> |
||||
|
</md-not-found> |
||||
|
<div ng-messages="theForm.entity.$error"> |
||||
|
<div ng-message="required">{{ requiredText | translate }}</div> |
||||
|
</div> |
||||
|
</md-autocomplete> |
||||
File diff suppressed because it is too large
@ -0,0 +1,277 @@ |
|||||
|
/* |
||||
|
* Copyright © 2016-2017 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. |
||||
|
*/ |
||||
|
/* eslint-disable import/no-unresolved, import/default */ |
||||
|
|
||||
|
import dashboardLayoutTemplate from './dashboard-layout.tpl.html'; |
||||
|
|
||||
|
/* eslint-enable import/no-unresolved, import/default */ |
||||
|
|
||||
|
/*@ngInject*/ |
||||
|
export default function DashboardLayout() { |
||||
|
return { |
||||
|
restrict: "E", |
||||
|
scope: true, |
||||
|
bindToController: { |
||||
|
layoutCtx: '=', |
||||
|
dashboardCtx: '=', |
||||
|
isEdit: '=', |
||||
|
isMobile: '=', |
||||
|
widgetEditMode: '=', |
||||
|
getStDiff: '&?' |
||||
|
}, |
||||
|
controller: DashboardLayoutController, |
||||
|
controllerAs: 'vm', |
||||
|
templateUrl: dashboardLayoutTemplate |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/*@ngInject*/ |
||||
|
function DashboardLayoutController($scope, $rootScope, $translate, $window, hotkeys, itembuffer) { |
||||
|
|
||||
|
var vm = this; |
||||
|
|
||||
|
vm.noData = noData; |
||||
|
vm.addWidget = addWidget; |
||||
|
vm.editWidget = editWidget; |
||||
|
vm.exportWidget = exportWidget; |
||||
|
vm.widgetMouseDown = widgetMouseDown; |
||||
|
vm.widgetClicked = widgetClicked; |
||||
|
vm.prepareDashboardContextMenu = prepareDashboardContextMenu; |
||||
|
vm.prepareWidgetContextMenu = prepareWidgetContextMenu; |
||||
|
vm.removeWidget = removeWidget; |
||||
|
vm.pasteWidget = pasteWidget; |
||||
|
vm.pasteWidgetReference = pasteWidgetReference; |
||||
|
vm.dashboardInited = dashboardInited; |
||||
|
vm.dashboardInitFailed = dashboardInitFailed; |
||||
|
|
||||
|
vm.reload = function() { |
||||
|
if (vm.dashboardContainer) { |
||||
|
vm.dashboardContainer.reload(); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
vm.setResizing = function(resizing) { |
||||
|
if (vm.dashboardContainer) { |
||||
|
vm.dashboardContainer.isResizing = resizing; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
vm.resetHighlight = function() { |
||||
|
if (vm.dashboardContainer) { |
||||
|
vm.dashboardContainer.resetHighlight(); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
vm.highlightWidget = function(widget, delay) { |
||||
|
if (vm.dashboardContainer) { |
||||
|
vm.dashboardContainer.highlightWidget(widget, delay); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
vm.selectWidget = function(widget, delay) { |
||||
|
if (vm.dashboardContainer) { |
||||
|
vm.dashboardContainer.selectWidget(widget, delay); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
vm.dashboardInitComplete = false; |
||||
|
|
||||
|
initHotKeys(); |
||||
|
|
||||
|
$scope.$on('$destroy', function() { |
||||
|
vm.dashboardContainer = null; |
||||
|
}); |
||||
|
|
||||
|
$scope.$watch('vm.layoutCtx', function () { |
||||
|
if (vm.layoutCtx) { |
||||
|
vm.layoutCtx.ctrl = vm; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
function noData() { |
||||
|
return vm.dashboardInitComplete && vm.layoutCtx && |
||||
|
vm.layoutCtx.widgets && vm.layoutCtx.widgets.length == 0; |
||||
|
} |
||||
|
|
||||
|
function addWidget($event) { |
||||
|
if (vm.dashboardCtx.onAddWidget) { |
||||
|
vm.dashboardCtx.onAddWidget($event, vm.layoutCtx); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function editWidget($event, widget) { |
||||
|
if (vm.dashboardCtx.onEditWidget) { |
||||
|
vm.dashboardCtx.onEditWidget($event, vm.layoutCtx, widget); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function exportWidget($event, widget) { |
||||
|
if (vm.dashboardCtx.onExportWidget) { |
||||
|
vm.dashboardCtx.onExportWidget($event, vm.layoutCtx, widget); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function widgetMouseDown($event, widget) { |
||||
|
if (vm.dashboardCtx.onWidgetMouseDown) { |
||||
|
vm.dashboardCtx.onWidgetMouseDown($event, vm.layoutCtx, widget); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function widgetClicked($event, widget) { |
||||
|
if (vm.dashboardCtx.onWidgetClicked) { |
||||
|
vm.dashboardCtx.onWidgetClicked($event, vm.layoutCtx, widget); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function prepareDashboardContextMenu() { |
||||
|
if (vm.dashboardCtx.prepareDashboardContextMenu) { |
||||
|
return vm.dashboardCtx.prepareDashboardContextMenu(vm.layoutCtx); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function prepareWidgetContextMenu(widget) { |
||||
|
if (vm.dashboardCtx.prepareWidgetContextMenu) { |
||||
|
return vm.dashboardCtx.prepareWidgetContextMenu(vm.layoutCtx, widget); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function removeWidget($event, widget) { |
||||
|
if (vm.dashboardCtx.onRemoveWidget) { |
||||
|
vm.dashboardCtx.onRemoveWidget($event, vm.layoutCtx, widget); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function dashboardInitFailed() { |
||||
|
var parentScope = $window.parent.angular.element($window.frameElement).scope(); |
||||
|
parentScope.$emit('widgetEditModeInited'); |
||||
|
parentScope.$apply(); |
||||
|
vm.dashboardInitComplete = true; |
||||
|
} |
||||
|
|
||||
|
function dashboardInited(dashboardContainer) { |
||||
|
vm.dashboardContainer = dashboardContainer; |
||||
|
vm.dashboardInitComplete = true; |
||||
|
} |
||||
|
|
||||
|
function isHotKeyAllowed(event) { |
||||
|
var target = event.target || event.srcElement; |
||||
|
var scope = angular.element(target).scope(); |
||||
|
return scope && scope.$parent !== $rootScope; |
||||
|
} |
||||
|
|
||||
|
function initHotKeys() { |
||||
|
$translate(['action.copy', 'action.paste', 'action.delete']).then(function (translations) { |
||||
|
hotkeys.bindTo($scope) |
||||
|
.add({ |
||||
|
combo: 'ctrl+c', |
||||
|
description: translations['action.copy'], |
||||
|
callback: function (event) { |
||||
|
if (isHotKeyAllowed(event) && |
||||
|
vm.isEdit && !vm.isEditingWidget && !vm.widgetEditMode) { |
||||
|
var widget = vm.dashboardContainer.getSelectedWidget(); |
||||
|
if (widget) { |
||||
|
event.preventDefault(); |
||||
|
copyWidget(event, widget); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
.add({ |
||||
|
combo: 'ctrl+r', |
||||
|
description: translations['action.copy-reference'], |
||||
|
callback: function (event) { |
||||
|
if (isHotKeyAllowed(event) && |
||||
|
vm.isEdit && !vm.isEditingWidget && !vm.widgetEditMode) { |
||||
|
var widget = vm.dashboardContainer.getSelectedWidget(); |
||||
|
if (widget) { |
||||
|
event.preventDefault(); |
||||
|
copyWidgetReference(event, widget); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
.add({ |
||||
|
combo: 'ctrl+v', |
||||
|
description: translations['action.paste'], |
||||
|
callback: function (event) { |
||||
|
if (isHotKeyAllowed(event) && |
||||
|
vm.isEdit && !vm.isEditingWidget && !vm.widgetEditMode) { |
||||
|
if (itembuffer.hasWidget()) { |
||||
|
event.preventDefault(); |
||||
|
pasteWidget(event); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
.add({ |
||||
|
combo: 'ctrl+i', |
||||
|
description: translations['action.paste-reference'], |
||||
|
callback: function (event) { |
||||
|
if (isHotKeyAllowed(event) && |
||||
|
vm.isEdit && !vm.isEditingWidget && !vm.widgetEditMode) { |
||||
|
if (itembuffer.canPasteWidgetReference(vm.dashboardCtx.dashboard, |
||||
|
vm.dashboardCtx.state, vm.layoutCtx.id)) { |
||||
|
event.preventDefault(); |
||||
|
pasteWidgetReference(event); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
.add({ |
||||
|
combo: 'ctrl+x', |
||||
|
description: translations['action.delete'], |
||||
|
callback: function (event) { |
||||
|
if (isHotKeyAllowed(event) && |
||||
|
vm.isEdit && !vm.isEditingWidget && !vm.widgetEditMode) { |
||||
|
var widget = vm.dashboardContainer.getSelectedWidget(); |
||||
|
if (widget) { |
||||
|
event.preventDefault(); |
||||
|
vm.dashboardCtx.onRemoveWidget(event, vm.layoutCtx, widget); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function copyWidget($event, widget) { |
||||
|
if (vm.dashboardCtx.copyWidget) { |
||||
|
vm.dashboardCtx.copyWidget($event, vm.layoutCtx, widget); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function copyWidgetReference($event, widget) { |
||||
|
if (vm.dashboardCtx.copyWidgetReference) { |
||||
|
vm.dashboardCtx.copyWidgetReference($event, vm.layoutCtx, widget); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function pasteWidget($event) { |
||||
|
var pos = vm.dashboardContainer.getEventGridPosition($event); |
||||
|
if (vm.dashboardCtx.pasteWidget) { |
||||
|
vm.dashboardCtx.pasteWidget($event, vm.layoutCtx, pos); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function pasteWidgetReference($event) { |
||||
|
var pos = vm.dashboardContainer.getEventGridPosition($event); |
||||
|
if (vm.dashboardCtx.pasteWidgetReference) { |
||||
|
vm.dashboardCtx.pasteWidgetReference($event, vm.layoutCtx, pos); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,69 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2017 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. |
||||
|
|
||||
|
--> |
||||
|
<md-content style="position: relative; width: 100%; height: 100%;" |
||||
|
ng-style="{'background-color': vm.layoutCtx.gridSettings.backgroundColor, |
||||
|
'background-image': 'url('+vm.layoutCtx.gridSettings.backgroundImageUrl+')', |
||||
|
'background-repeat': 'no-repeat', |
||||
|
'background-attachment': 'scroll', |
||||
|
'background-size': vm.layoutCtx.gridSettings.backgroundSizeMode || '100%', |
||||
|
'background-position': '0% 0%'}"> |
||||
|
<section ng-show="!loading && vm.noData()" layout-align="center center" |
||||
|
ng-style="{'color': vm.layoutCtx.gridSettings.color}" |
||||
|
style="text-transform: uppercase; display: flex; z-index: 1; pointer-events: none;" |
||||
|
class="md-headline tb-absolute-fill"> |
||||
|
<span translate ng-if="!vm.isEdit"> |
||||
|
dashboard.no-widgets |
||||
|
</span> |
||||
|
<md-button ng-if="vm.isEdit && !vm.widgetEditMode" class="tb-add-new-widget" ng-click="vm.addWidget({event: $event})"> |
||||
|
<md-icon aria-label="{{ 'action.add' | translate }}" class="material-icons tb-md-96">add</md-icon> |
||||
|
{{ 'dashboard.add-widget' | translate }} |
||||
|
</md-button> |
||||
|
</section> |
||||
|
<tb-dashboard |
||||
|
dashboard-style="{'background-color': vm.layoutCtx.gridSettings.backgroundColor, |
||||
|
'background-image': 'url('+vm.layoutCtx.gridSettings.backgroundImageUrl+')', |
||||
|
'background-repeat': 'no-repeat', |
||||
|
'background-attachment': 'scroll', |
||||
|
'background-size': vm.layoutCtx.gridSettings.backgroundSizeMode || '100%', |
||||
|
'background-position': '0% 0%'}" |
||||
|
widgets="vm.layoutCtx.widgets" |
||||
|
widget-layouts="vm.layoutCtx.widgetLayouts" |
||||
|
columns="vm.layoutCtx.gridSettings.columns" |
||||
|
margins="vm.layoutCtx.gridSettings.margins" |
||||
|
aliases-info="vm.dashboardCtx.aliasesInfo" |
||||
|
state-controller="vm.dashboardCtx.stateController" |
||||
|
dashboard-timewindow="vm.dashboardCtx.dashboardTimewindow" |
||||
|
is-edit="vm.isEdit" |
||||
|
is-mobile="vm.isMobile" |
||||
|
is-mobile-disabled="vm.widgetEditMode" |
||||
|
is-edit-action-enabled="vm.isEdit" |
||||
|
is-export-action-enabled="vm.isEdit && !vm.widgetEditMode" |
||||
|
is-remove-action-enabled="vm.isEdit && !vm.widgetEditMode" |
||||
|
on-edit-widget="vm.editWidget(event, widget)" |
||||
|
on-export-widget="vm.exportWidget(event, widget)" |
||||
|
on-widget-mouse-down="vm.widgetMouseDown(event, widget)" |
||||
|
on-widget-clicked="vm.widgetClicked(event, widget)" |
||||
|
prepare-dashboard-context-menu="vm.prepareDashboardContextMenu()" |
||||
|
prepare-widget-context-menu="vm.prepareWidgetContextMenu(widget)" |
||||
|
on-remove-widget="vm.removeWidget(event, widget)" |
||||
|
get-st-diff="vm.getStDiff()" |
||||
|
on-init="vm.dashboardInited(dashboard)" |
||||
|
on-init-failed="vm.dashboardInitFailed(e)" |
||||
|
ignore-loading="vm.layoutCtx.ignoreLoading"> |
||||
|
</tb-dashboard> |
||||
|
</md-content> |
||||
@ -0,0 +1,25 @@ |
|||||
|
/* |
||||
|
* Copyright © 2016-2017 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 ManageDashboardLayoutsController from './manage-dashboard-layouts.controller'; |
||||
|
import SelectTargetLayoutController from './select-target-layout.controller'; |
||||
|
import DashboardLayoutDirective from './dashboard-layout.directive'; |
||||
|
|
||||
|
export default angular.module('thingsboard.dashboard.layouts', []) |
||||
|
.controller('ManageDashboardLayoutsController', ManageDashboardLayoutsController) |
||||
|
.controller('SelectTargetLayoutController', SelectTargetLayoutController) |
||||
|
.directive('tbDashboardLayout', DashboardLayoutDirective) |
||||
|
.name; |
||||
@ -0,0 +1,83 @@ |
|||||
|
/* |
||||
|
* Copyright © 2016-2017 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. |
||||
|
*/ |
||||
|
|
||||
|
/* eslint-disable import/no-unresolved, import/default */ |
||||
|
|
||||
|
import dashboardSettingsTemplate from '../dashboard-settings.tpl.html'; |
||||
|
|
||||
|
/* eslint-enable import/no-unresolved, import/default */ |
||||
|
|
||||
|
/*@ngInject*/ |
||||
|
export default function ManageDashboardLayoutsController($scope, $mdDialog, $document, dashboardUtils, layouts) { |
||||
|
|
||||
|
var vm = this; |
||||
|
|
||||
|
vm.openLayoutSettings = openLayoutSettings; |
||||
|
vm.cancel = cancel; |
||||
|
vm.save = save; |
||||
|
|
||||
|
vm.layouts = layouts; |
||||
|
vm.displayLayouts = { |
||||
|
main: angular.isDefined(vm.layouts.main), |
||||
|
right: angular.isDefined(vm.layouts.right) |
||||
|
} |
||||
|
|
||||
|
for (var l in vm.displayLayouts) { |
||||
|
if (!vm.layouts[l]) { |
||||
|
vm.layouts[l] = dashboardUtils.createDefaultLayoutData(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function openLayoutSettings($event, layoutId) { |
||||
|
var gridSettings = angular.copy(vm.layouts[layoutId].gridSettings); |
||||
|
$mdDialog.show({ |
||||
|
controller: 'DashboardSettingsController', |
||||
|
controllerAs: 'vm', |
||||
|
templateUrl: dashboardSettingsTemplate, |
||||
|
locals: { |
||||
|
settings: null, |
||||
|
gridSettings: gridSettings |
||||
|
}, |
||||
|
parent: angular.element($document[0].body), |
||||
|
skipHide: true, |
||||
|
fullscreen: true, |
||||
|
targetEvent: $event |
||||
|
}).then(function (data) { |
||||
|
var gridSettings = data.gridSettings; |
||||
|
if (gridSettings) { |
||||
|
dashboardUtils.updateLayoutSettings(vm.layouts[layoutId], gridSettings); |
||||
|
} |
||||
|
$scope.theForm.$setDirty(); |
||||
|
}, function () { |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function cancel() { |
||||
|
$mdDialog.cancel(); |
||||
|
} |
||||
|
|
||||
|
function save() { |
||||
|
$scope.theForm.$setPristine(); |
||||
|
for (var l in vm.displayLayouts) { |
||||
|
if (!vm.displayLayouts[l]) { |
||||
|
if (vm.layouts[l]) { |
||||
|
delete vm.layouts[l]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
$mdDialog.hide(vm.layouts); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,65 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2017 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. |
||||
|
|
||||
|
--> |
||||
|
<md-dialog aria-label="{{ 'layout.manage' | translate }}"> |
||||
|
<form name="theForm" ng-submit="vm.save()"> |
||||
|
<md-toolbar> |
||||
|
<div class="md-toolbar-tools"> |
||||
|
<h2 translate>{{ 'layout.manage' }}</h2> |
||||
|
<span flex></span> |
||||
|
<md-button class="md-icon-button" ng-click="vm.cancel()"> |
||||
|
<ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon> |
||||
|
</md-button> |
||||
|
</div> |
||||
|
</md-toolbar> |
||||
|
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear> |
||||
|
<span style="min-height: 5px;" flex="" ng-show="!loading"></span> |
||||
|
<md-dialog-content> |
||||
|
<div class="md-dialog-content"> |
||||
|
<fieldset ng-disabled="loading"> |
||||
|
<div layout="row" layout-align="start center"> |
||||
|
<md-checkbox ng-disabled="true" flex aria-label="{{ 'layout.main' | translate }}" |
||||
|
ng-model="vm.displayLayouts.main">{{ 'layout.main' | translate }} |
||||
|
</md-checkbox> |
||||
|
<md-checkbox flex aria-label="{{ 'layout.right' | translate }}" |
||||
|
ng-model="vm.displayLayouts.right">{{ 'layout.right' | translate }} |
||||
|
</md-checkbox> |
||||
|
</div> |
||||
|
<div layout="row" layout-align="start center"> |
||||
|
<md-button flex ng-show="vm.displayLayouts.main" |
||||
|
class="tb-layout-button md-raised md-primary" layout="column" |
||||
|
ng-click="vm.openLayoutSettings($event, 'main')"> |
||||
|
<span translate>layout.main</span> |
||||
|
</md-button> |
||||
|
<md-button flex ng-show="vm.displayLayouts.right" |
||||
|
class="tb-layout-button md-raised md-primary" layout="column" |
||||
|
ng-click="vm.openLayoutSettings($event, 'right')"> |
||||
|
<span translate>layout.right</span> |
||||
|
</md-button> |
||||
|
</div> |
||||
|
</fieldset> |
||||
|
</div> |
||||
|
</md-dialog-content> |
||||
|
<md-dialog-actions layout="row"> |
||||
|
<span flex></span> |
||||
|
<md-button ng-disabled="loading || !theForm.$dirty || !theForm.$valid" type="submit" class="md-raised md-primary"> |
||||
|
{{ 'action.save' | translate }} |
||||
|
</md-button> |
||||
|
<md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }}</md-button> |
||||
|
</md-dialog-actions> |
||||
|
</form> |
||||
|
</md-dialog> |
||||
@ -0,0 +1,33 @@ |
|||||
|
/* |
||||
|
* Copyright © 2016-2017 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. |
||||
|
*/ |
||||
|
|
||||
|
/*@ngInject*/ |
||||
|
export default function SelectTargetLayoutController($scope, $mdDialog) { |
||||
|
|
||||
|
var vm = this; |
||||
|
|
||||
|
vm.cancel = cancel; |
||||
|
vm.selectLayout = selectLayout; |
||||
|
|
||||
|
function cancel() { |
||||
|
$mdDialog.cancel(); |
||||
|
} |
||||
|
|
||||
|
function selectLayout($event, layoutId) { |
||||
|
$scope.theForm.$setPristine(); |
||||
|
$mdDialog.hide(layoutId); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,48 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2017 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. |
||||
|
|
||||
|
--> |
||||
|
<md-dialog aria-label="{{ 'layout.select' | translate }}"> |
||||
|
<form name="theForm" ng-submit="vm.save()"> |
||||
|
<md-toolbar> |
||||
|
<div class="md-toolbar-tools"> |
||||
|
<h2 translate>{{ 'layout.select' }}</h2> |
||||
|
<span flex></span> |
||||
|
<md-button class="md-icon-button" ng-click="vm.cancel()"> |
||||
|
<ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon> |
||||
|
</md-button> |
||||
|
</div> |
||||
|
</md-toolbar> |
||||
|
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear> |
||||
|
<span style="min-height: 5px;" flex="" ng-show="!loading"></span> |
||||
|
<md-dialog-content> |
||||
|
<div class="md-dialog-content"> |
||||
|
<fieldset ng-disabled="loading"> |
||||
|
<div layout="row" layout-align="start center"> |
||||
|
<md-button flex class="tb-layout-button md-raised md-primary" layout="column" |
||||
|
ng-click="vm.selectLayout($event, 'main')"> |
||||
|
<span translate>layout.main</span> |
||||
|
</md-button> |
||||
|
<md-button flex class="tb-layout-button md-raised md-primary" layout="column" |
||||
|
ng-click="vm.selectLayout($event, 'right')"> |
||||
|
<span translate>layout.right</span> |
||||
|
</md-button> |
||||
|
</div> |
||||
|
</fieldset> |
||||
|
</div> |
||||
|
</md-dialog-content> |
||||
|
</form> |
||||
|
</md-dialog> |
||||
@ -0,0 +1,82 @@ |
|||||
|
/* |
||||
|
* Copyright © 2016-2017 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. |
||||
|
*/ |
||||
|
|
||||
|
/*@ngInject*/ |
||||
|
export default function DashboardStateDialogController($scope, $mdDialog, $filter, dashboardUtils, isAdd, allStates, state) { |
||||
|
|
||||
|
var vm = this; |
||||
|
|
||||
|
vm.isAdd = isAdd; |
||||
|
vm.allStates = allStates; |
||||
|
vm.state = state; |
||||
|
|
||||
|
vm.stateIdTouched = false; |
||||
|
|
||||
|
if (vm.isAdd) { |
||||
|
vm.state = dashboardUtils.createDefaultState('', false); |
||||
|
vm.state.id = ''; |
||||
|
vm.prevStateId = ''; |
||||
|
} else { |
||||
|
vm.state = state; |
||||
|
vm.prevStateId = vm.state.id; |
||||
|
} |
||||
|
|
||||
|
vm.cancel = cancel; |
||||
|
vm.save = save; |
||||
|
|
||||
|
$scope.$watch("vm.state.name", function(newVal, prevVal) { |
||||
|
if (!angular.equals(newVal, prevVal) && vm.state.name != null) { |
||||
|
checkStateName(); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
$scope.$watch("vm.state.id", function(newVal, prevVal) { |
||||
|
if (!angular.equals(newVal, prevVal) && vm.state.id != null) { |
||||
|
checkStateId(); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
function checkStateName() { |
||||
|
if (!vm.stateIdTouched && vm.isAdd) { |
||||
|
vm.state.id = vm.state.name.toLowerCase().replace(/\W/g,"_"); |
||||
|
} |
||||
|
var result = $filter('filter')(vm.allStates, {name: vm.state.name}, true); |
||||
|
if (result && result.length && result[0].id !== vm.prevStateId) { |
||||
|
$scope.theForm.name.$setValidity('stateExists', false); |
||||
|
} else { |
||||
|
$scope.theForm.name.$setValidity('stateExists', true); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function checkStateId() { |
||||
|
var result = $filter('filter')(vm.allStates, {id: vm.state.id}, true); |
||||
|
if (result && result.length && result[0].id !== vm.prevStateId) { |
||||
|
$scope.theForm.stateId.$setValidity('stateExists', false); |
||||
|
} else { |
||||
|
$scope.theForm.stateId.$setValidity('stateExists', true); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function cancel() { |
||||
|
$mdDialog.cancel(); |
||||
|
} |
||||
|
|
||||
|
function save() { |
||||
|
$scope.theForm.$setPristine(); |
||||
|
vm.state.id = vm.state.id.trim(); |
||||
|
$mdDialog.hide(vm.state); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,72 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2017 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. |
||||
|
|
||||
|
--> |
||||
|
<md-dialog class="dashboard-state" aria-label="{{'dashboard.state' | translate }}" style="min-width: 600px;"> |
||||
|
<form name="theForm" ng-submit="vm.save()"> |
||||
|
<md-toolbar> |
||||
|
<div class="md-toolbar-tools"> |
||||
|
<h2>{{ (vm.isAdd ? 'dashboard.add-state' : 'dashboard.edit-state') | translate }}</h2> |
||||
|
<span flex></span> |
||||
|
<md-button class="md-icon-button" ng-click="vm.cancel()"> |
||||
|
<ng-md-icon icon="close" aria-label="{{ 'action.close' | translate }}"></ng-md-icon> |
||||
|
</md-button> |
||||
|
</div> |
||||
|
</md-toolbar> |
||||
|
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear> |
||||
|
<span style="min-height: 5px;" flex="" ng-show="!loading"></span> |
||||
|
<md-dialog-content> |
||||
|
<div class="md-dialog-content"> |
||||
|
<md-content class="md-padding" layout="column"> |
||||
|
<fieldset ng-disabled="loading"> |
||||
|
<md-input-container class="md-block"> |
||||
|
<label translate>dashboard.state-name</label> |
||||
|
<input name="name" required ng-model="vm.state.name"> |
||||
|
<div ng-messages="theForm.name.$error"> |
||||
|
<div ng-message="required" translate>dashboard.state-name-required</div> |
||||
|
<div ng-message="stateExists" translate>dashboard.state-name-exists</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-input-container class="md-block"> |
||||
|
<label translate>dashboard.state-id</label> |
||||
|
<input name="stateId" ng-model="vm.state.id" |
||||
|
ng-change="vm.stateIdTouched = true" |
||||
|
ng-pattern="/^[a-zA-Z0-9_]*$/"> |
||||
|
<div ng-messages="theForm.stateId.$error"> |
||||
|
<div ng-message="required" translate>dashboard.state-id-required</div> |
||||
|
<div ng-message="stateExists" translate>dashboard.state-id-exists</div> |
||||
|
<div ng-message="pattern" translate>dashboard.invalid-state-id-format</div> |
||||
|
</div> |
||||
|
</md-input-container> |
||||
|
<md-checkbox flex aria-label="{{ 'dashboard.is-root-state' | translate }}" |
||||
|
ng-model="vm.state.root">{{ 'dashboard.is-root-state' | translate }} |
||||
|
</md-checkbox> |
||||
|
</fieldset> |
||||
|
</md-content> |
||||
|
</div> |
||||
|
</md-dialog-content> |
||||
|
<md-dialog-actions layout="row"> |
||||
|
<span flex></span> |
||||
|
<md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit" |
||||
|
class="md-raised md-primary"> |
||||
|
{{ vm.isAdd ? 'Add' : 'Save' }} |
||||
|
</md-button> |
||||
|
<md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;"> |
||||
|
Cancel |
||||
|
</md-button> |
||||
|
</md-dialog-actions> |
||||
|
</form> |
||||
|
</md-dialog> |
||||
@ -0,0 +1,181 @@ |
|||||
|
/* |
||||
|
* Copyright © 2016-2017 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. |
||||
|
*/ |
||||
|
|
||||
|
/*@ngInject*/ |
||||
|
export default function DefaultStateController($scope, $location, $state, $stateParams, $translate, types, dashboardUtils) { |
||||
|
|
||||
|
var vm = this; |
||||
|
|
||||
|
vm.inited = false; |
||||
|
|
||||
|
vm.openState = openState; |
||||
|
vm.updateState = updateState; |
||||
|
vm.navigatePrevState = navigatePrevState; |
||||
|
vm.getStateId = getStateId; |
||||
|
vm.getStateParams = getStateParams; |
||||
|
|
||||
|
vm.getStateName = getStateName; |
||||
|
|
||||
|
vm.displayStateSelection = displayStateSelection; |
||||
|
|
||||
|
function openState(id, params) { |
||||
|
if (vm.states && vm.states[id]) { |
||||
|
if (!params) { |
||||
|
params = {}; |
||||
|
} |
||||
|
var newState = { |
||||
|
id: id, |
||||
|
params: params |
||||
|
} |
||||
|
//append new state
|
||||
|
vm.stateObject[0] = newState; |
||||
|
gotoState(vm.stateObject[0].id, true); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function updateState(id, params) { |
||||
|
if (vm.states && vm.states[id]) { |
||||
|
if (!params) { |
||||
|
params = {}; |
||||
|
} |
||||
|
var newState = { |
||||
|
id: id, |
||||
|
params: params |
||||
|
} |
||||
|
//replace with new state
|
||||
|
vm.stateObject[0] = newState; |
||||
|
gotoState(vm.stateObject[0].id, true); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function navigatePrevState(index) { |
||||
|
if (index < vm.stateObject.length-1) { |
||||
|
vm.stateObject.splice(index+1, vm.stateObject.length-index-1); |
||||
|
gotoState(vm.stateObject[vm.stateObject.length-1].id, true); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function getStateId() { |
||||
|
return vm.stateObject[vm.stateObject.length-1].id; |
||||
|
} |
||||
|
|
||||
|
function getStateParams() { |
||||
|
return vm.stateObject[vm.stateObject.length-1].params; |
||||
|
} |
||||
|
|
||||
|
function getStateName(id, state) { |
||||
|
var result = ''; |
||||
|
var translationId = types.translate.dashboardStatePrefix + id; |
||||
|
var translation = $translate.instant(translationId); |
||||
|
if (translation != translationId) { |
||||
|
result = translation; |
||||
|
} else { |
||||
|
result = state.name; |
||||
|
} |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
function parseState(stateJson) { |
||||
|
var result; |
||||
|
if (stateJson) { |
||||
|
try { |
||||
|
result = angular.fromJson(stateJson); |
||||
|
} catch (e) { |
||||
|
result = [ { id: null, params: {} } ]; |
||||
|
} |
||||
|
} |
||||
|
if (!result) { |
||||
|
result = []; |
||||
|
} |
||||
|
if (!result.length) { |
||||
|
result[0] = { id: null, params: {} } |
||||
|
} |
||||
|
if (!result[0].id) { |
||||
|
result[0].id = dashboardUtils.getRootStateId(vm.states); |
||||
|
} |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
$scope.$watch('vm.states', function() { |
||||
|
if (vm.states) { |
||||
|
if (!vm.inited) { |
||||
|
vm.inited = true; |
||||
|
init(); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
function displayStateSelection() { |
||||
|
return vm.states && Object.keys(vm.states).length > 1; |
||||
|
} |
||||
|
|
||||
|
function init() { |
||||
|
var initialState = $stateParams.state; |
||||
|
vm.stateObject = parseState(initialState); |
||||
|
|
||||
|
gotoState(vm.stateObject[0].id, false); |
||||
|
|
||||
|
$scope.$watchCollection(function(){ |
||||
|
return $state.params; |
||||
|
}, function(){ |
||||
|
var currentState = $state.params.state; |
||||
|
vm.stateObject = parseState(currentState); |
||||
|
}); |
||||
|
|
||||
|
$scope.$watch('vm.dashboardCtrl.dashboardCtx.state', function() { |
||||
|
if (vm.stateObject[0].id !== vm.dashboardCtrl.dashboardCtx.state) { |
||||
|
stopWatchStateObject(); |
||||
|
vm.stateObject[0].id = vm.dashboardCtrl.dashboardCtx.state; |
||||
|
updateLocation(); |
||||
|
watchStateObject(); |
||||
|
} |
||||
|
}); |
||||
|
watchStateObject(); |
||||
|
} |
||||
|
|
||||
|
function stopWatchStateObject() { |
||||
|
if (vm.stateObjectWatcher) { |
||||
|
vm.stateObjectWatcher(); |
||||
|
vm.stateObjectWatcher = null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function watchStateObject() { |
||||
|
vm.stateObjectWatcher = $scope.$watch('vm.stateObject', function(newVal, prevVal) { |
||||
|
if (!angular.equals(newVal, prevVal) && newVal) { |
||||
|
gotoState(vm.stateObject[0].id, true); |
||||
|
} |
||||
|
}, true); |
||||
|
} |
||||
|
|
||||
|
function gotoState(stateId, update) { |
||||
|
if (vm.dashboardCtrl.dashboardCtx.state != stateId) { |
||||
|
vm.dashboardCtrl.openDashboardState(stateId); |
||||
|
if (update) { |
||||
|
updateLocation(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function updateLocation() { |
||||
|
if (vm.stateObject[0].id) { |
||||
|
$location.search({state : angular.toJson(vm.stateObject)}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2017 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. |
||||
|
|
||||
|
--> |
||||
|
<md-select ng-show="vm.displayStateSelection()" aria-label="{{ 'dashboard.state' | translate }}" ng-model="vm.stateObject[0].id"> |
||||
|
<md-option ng-repeat="(stateId, state) in vm.states" ng-value="stateId"> |
||||
|
{{vm.getStateName(stateId, state)}} |
||||
|
</md-option> |
||||
|
</md-select> |
||||
@ -0,0 +1,243 @@ |
|||||
|
/* |
||||
|
* Copyright © 2016-2017 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 './entity-state-controller.scss'; |
||||
|
|
||||
|
/*@ngInject*/ |
||||
|
export default function EntityStateController($scope, $location, $state, $stateParams, $q, $translate, types, dashboardUtils, entityService) { |
||||
|
|
||||
|
var vm = this; |
||||
|
|
||||
|
vm.inited = false; |
||||
|
|
||||
|
vm.openState = openState; |
||||
|
vm.updateState = updateState; |
||||
|
vm.navigatePrevState = navigatePrevState; |
||||
|
vm.getStateId = getStateId; |
||||
|
vm.getStateParams = getStateParams; |
||||
|
|
||||
|
vm.getStateName = getStateName; |
||||
|
|
||||
|
vm.selectedStateIndex = -1; |
||||
|
|
||||
|
function openState(id, params) { |
||||
|
if (vm.states && vm.states[id]) { |
||||
|
resolveEntity(params).then( |
||||
|
function success(entityName) { |
||||
|
params.entityName = entityName; |
||||
|
var newState = { |
||||
|
id: id, |
||||
|
params: params |
||||
|
} |
||||
|
//append new state
|
||||
|
vm.stateObject.push(newState); |
||||
|
vm.selectedStateIndex = vm.stateObject.length-1; |
||||
|
gotoState(vm.stateObject[vm.stateObject.length-1].id, true); |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function updateState(id, params) { |
||||
|
if (vm.states && vm.states[id]) { |
||||
|
resolveEntity(params).then( |
||||
|
function success(entityName) { |
||||
|
params.entityName = entityName; |
||||
|
var newState = { |
||||
|
id: id, |
||||
|
params: params |
||||
|
} |
||||
|
//replace with new state
|
||||
|
vm.stateObject[vm.stateObject.length - 1] = newState; |
||||
|
gotoState(vm.stateObject[vm.stateObject.length - 1].id, true); |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function navigatePrevState(index) { |
||||
|
if (index < vm.stateObject.length-1) { |
||||
|
vm.stateObject.splice(index+1, vm.stateObject.length-index-1); |
||||
|
vm.selectedStateIndex = vm.stateObject.length-1; |
||||
|
gotoState(vm.stateObject[vm.stateObject.length-1].id, true); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function getStateId() { |
||||
|
return vm.stateObject[vm.stateObject.length-1].id; |
||||
|
} |
||||
|
|
||||
|
function getStateParams() { |
||||
|
return vm.stateObject[vm.stateObject.length-1].params; |
||||
|
} |
||||
|
|
||||
|
function getStateName(index) { |
||||
|
var result = ''; |
||||
|
if (vm.stateObject[index]) { |
||||
|
var params = vm.stateObject[index].params; |
||||
|
if (params && params.entityName) { |
||||
|
result = params.entityName; |
||||
|
} else { |
||||
|
var id = vm.stateObject[index].id; |
||||
|
var translationId = types.translate.dashboardStatePrefix + id; |
||||
|
var translation = $translate.instant(translationId); |
||||
|
if (translation != translationId) { |
||||
|
result = translation; |
||||
|
} else { |
||||
|
result = vm.states[vm.stateObject[index].id].name; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
function resolveEntity(params) { |
||||
|
var deferred = $q.defer(); |
||||
|
if (params && params.entityId && params.entityId.id && params.entityId.entityType) { |
||||
|
entityService.getEntity(params.entityId.entityType, params.entityId.id, {ignoreLoading: true, ignoreErrors: true}).then( |
||||
|
function success(entity) { |
||||
|
var entityName = entityService.entityName(params.entityId.entityType, entity); |
||||
|
deferred.resolve(entityName); |
||||
|
}, |
||||
|
function fail() { |
||||
|
deferred.reject(); |
||||
|
} |
||||
|
); |
||||
|
} else { |
||||
|
deferred.reject(); |
||||
|
} |
||||
|
return deferred.promise; |
||||
|
} |
||||
|
|
||||
|
function parseState(stateJson) { |
||||
|
var result; |
||||
|
if (stateJson) { |
||||
|
try { |
||||
|
result = angular.fromJson(stateJson); |
||||
|
} catch (e) { |
||||
|
result = [ { id: null, params: {} } ]; |
||||
|
} |
||||
|
} |
||||
|
if (!result) { |
||||
|
result = []; |
||||
|
} |
||||
|
if (!result.length) { |
||||
|
result[0] = { id: null, params: {} } |
||||
|
} |
||||
|
if (!result[0].id) { |
||||
|
result[0].id = dashboardUtils.getRootStateId(vm.states); |
||||
|
} |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
$scope.$watch('vm.states', function() { |
||||
|
if (vm.states) { |
||||
|
if (!vm.inited) { |
||||
|
vm.inited = true; |
||||
|
init(); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
function init() { |
||||
|
var initialState = $stateParams.state; |
||||
|
vm.stateObject = parseState(initialState); |
||||
|
vm.selectedStateIndex = vm.stateObject.length-1; |
||||
|
gotoState(vm.stateObject[vm.stateObject.length-1].id, false); |
||||
|
|
||||
|
$scope.$watchCollection(function() { |
||||
|
return $state.params; |
||||
|
}, function(){ |
||||
|
var currentState = $state.params.state; |
||||
|
vm.stateObject = parseState(currentState); |
||||
|
}); |
||||
|
|
||||
|
$scope.$watch('vm.dashboardCtrl.dashboardCtx.state', function() { |
||||
|
if (vm.stateObject[vm.stateObject.length-1].id !== vm.dashboardCtrl.dashboardCtx.state) { |
||||
|
stopWatchStateObject(); |
||||
|
vm.stateObject[vm.stateObject.length-1].id = vm.dashboardCtrl.dashboardCtx.state; |
||||
|
updateLocation(); |
||||
|
watchStateObject(); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
watchStateObject(); |
||||
|
|
||||
|
if (vm.dashboardCtrl.isMobile) { |
||||
|
watchSelectedStateIndex(); |
||||
|
} |
||||
|
|
||||
|
$scope.$watch('vm.dashboardCtrl.isMobile', function(newVal, prevVal) { |
||||
|
if (!angular.equals(newVal, prevVal)) { |
||||
|
if (vm.dashboardCtrl.isMobile) { |
||||
|
watchSelectedStateIndex(); |
||||
|
} else { |
||||
|
stopWatchSelectedStateIndex(); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
function stopWatchStateObject() { |
||||
|
if (vm.stateObjectWatcher) { |
||||
|
vm.stateObjectWatcher(); |
||||
|
vm.stateObjectWatcher = null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function watchStateObject() { |
||||
|
vm.stateObjectWatcher = $scope.$watch('vm.stateObject', function(newVal, prevVal) { |
||||
|
if (!angular.equals(newVal, prevVal) && newVal) { |
||||
|
vm.selectedStateIndex = vm.stateObject.length-1; |
||||
|
gotoState(vm.stateObject[vm.stateObject.length-1].id, true); |
||||
|
} |
||||
|
}, true); |
||||
|
} |
||||
|
|
||||
|
function stopWatchSelectedStateIndex() { |
||||
|
if (vm.selectedStateIndexWatcher) { |
||||
|
vm.selectedStateIndexWatcher(); |
||||
|
vm.selectedStateIndexWatcher = null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function watchSelectedStateIndex() { |
||||
|
vm.selectedStateIndexWatcher = $scope.$watch('vm.selectedStateIndex', function(newVal, prevVal) { |
||||
|
if (!angular.equals(newVal, prevVal)) { |
||||
|
navigatePrevState(vm.selectedStateIndex); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function gotoState(stateId, update) { |
||||
|
if (vm.dashboardCtrl.dashboardCtx.state != stateId) { |
||||
|
vm.dashboardCtrl.openDashboardState(stateId); |
||||
|
if (update) { |
||||
|
updateLocation(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function updateLocation() { |
||||
|
if (vm.stateObject[vm.stateObject.length-1].id) { |
||||
|
$location.search({state : angular.toJson(vm.stateObject)}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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. |
||||
|
*/ |
||||
|
|
||||
|
.entity-state-controller { |
||||
|
.state-divider { |
||||
|
font-size: 28px; |
||||
|
padding-left: 15px; |
||||
|
padding-right: 15px; |
||||
|
} |
||||
|
.state-entry { |
||||
|
font-size: 22px; |
||||
|
outline: none; |
||||
|
} |
||||
|
md-select { |
||||
|
.md-text { |
||||
|
font-size: 22px; |
||||
|
font-weight: bold; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2017 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="entity-state-controller"> |
||||
|
<div ng-if="!vm.dashboardCtrl.isMobile || vm.stateObject.length===1" layout="row" layout-align="start center"> |
||||
|
<div layout="row" layout-align="start center" ng-repeat="state in vm.stateObject track by $index"> |
||||
|
<span class='state-divider' ng-if="$index"> > </span> |
||||
|
<span class='state-entry' ng-style="{fontWeight: $last ? 'bold' : 'normal', |
||||
|
cursor: $last ? 'default' : 'pointer'}" ng-click="vm.navigatePrevState($index)"> |
||||
|
{{vm.getStateName($index)}} |
||||
|
</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
<md-select ng-if="vm.dashboardCtrl.isMobile && vm.stateObject.length > 1" aria-label="{{ 'dashboard.state' | translate }}" ng-model="vm.selectedStateIndex"> |
||||
|
<md-option ng-repeat="state in vm.stateObject track by $index" ng-value="$index"> |
||||
|
{{vm.getStateName($index)}} |
||||
|
</md-option> |
||||
|
</md-select> |
||||
|
</div> |
||||
@ -0,0 +1,29 @@ |
|||||
|
/* |
||||
|
* Copyright © 2016-2017 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 ManageDashboardStatesController from './manage-dashboard-states.controller'; |
||||
|
import DashboardStateDialogController from './dashboard-state-dialog.controller'; |
||||
|
import SelectTargetStateController from './select-target-state.controller'; |
||||
|
import StatesComponentDirective from './states-component.directive'; |
||||
|
import StatesControllerService from './states-controller.service'; |
||||
|
|
||||
|
export default angular.module('thingsboard.dashboard.states', []) |
||||
|
.controller('ManageDashboardStatesController', ManageDashboardStatesController) |
||||
|
.controller('DashboardStateDialogController', DashboardStateDialogController) |
||||
|
.controller('SelectTargetStateController', SelectTargetStateController) |
||||
|
.directive('tbStatesComponent', StatesComponentDirective) |
||||
|
.factory('statesControllerService', StatesControllerService) |
||||
|
.name; |
||||
@ -0,0 +1,198 @@ |
|||||
|
/* |
||||
|
* Copyright © 2016-2017 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 './manage-dashboard-states.scss'; |
||||
|
|
||||
|
/* eslint-disable import/no-unresolved, import/default */ |
||||
|
|
||||
|
import dashboardStateDialogTemplate from './dashboard-state-dialog.tpl.html'; |
||||
|
|
||||
|
/* eslint-enable import/no-unresolved, import/default */ |
||||
|
|
||||
|
/*@ngInject*/ |
||||
|
export default function ManageDashboardStatesController($scope, $mdDialog, $filter, $document, $translate, states) { |
||||
|
|
||||
|
var vm = this; |
||||
|
|
||||
|
vm.allStates = []; |
||||
|
for (var id in states) { |
||||
|
var state = states[id]; |
||||
|
state.id = id; |
||||
|
vm.allStates.push(state); |
||||
|
} |
||||
|
|
||||
|
vm.states = []; |
||||
|
vm.statesCount = 0; |
||||
|
|
||||
|
vm.query = { |
||||
|
order: 'name', |
||||
|
limit: 5, |
||||
|
page: 1, |
||||
|
search: null |
||||
|
}; |
||||
|
|
||||
|
vm.enterFilterMode = enterFilterMode; |
||||
|
vm.exitFilterMode = exitFilterMode; |
||||
|
vm.onReorder = onReorder; |
||||
|
vm.onPaginate = onPaginate; |
||||
|
vm.addState = addState; |
||||
|
vm.editState = editState; |
||||
|
vm.deleteState = deleteState; |
||||
|
|
||||
|
vm.cancel = cancel; |
||||
|
vm.save = save; |
||||
|
|
||||
|
$scope.$watch("vm.query.search", function(newVal, prevVal) { |
||||
|
if (!angular.equals(newVal, prevVal) && vm.query.search != null) { |
||||
|
updateStates(); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
updateStates (); |
||||
|
|
||||
|
function updateStates () { |
||||
|
var result = $filter('orderBy')(vm.allStates, vm.query.order); |
||||
|
if (vm.query.search != null) { |
||||
|
result = $filter('filter')(result, {$: vm.query.search}); |
||||
|
} |
||||
|
vm.statesCount = result.length; |
||||
|
var startIndex = vm.query.limit * (vm.query.page - 1); |
||||
|
vm.states = result.slice(startIndex, startIndex + vm.query.limit); |
||||
|
} |
||||
|
|
||||
|
function enterFilterMode () { |
||||
|
vm.query.search = ''; |
||||
|
} |
||||
|
|
||||
|
function exitFilterMode () { |
||||
|
vm.query.search = null; |
||||
|
updateStates(); |
||||
|
} |
||||
|
|
||||
|
function onReorder () { |
||||
|
updateStates(); |
||||
|
} |
||||
|
|
||||
|
function onPaginate () { |
||||
|
updateStates(); |
||||
|
} |
||||
|
|
||||
|
function addState ($event) { |
||||
|
openStateDialog($event, null, true); |
||||
|
} |
||||
|
|
||||
|
function editState ($event, alertRule) { |
||||
|
if ($event) { |
||||
|
$event.stopPropagation(); |
||||
|
} |
||||
|
openStateDialog($event, alertRule, false); |
||||
|
} |
||||
|
|
||||
|
function openStateDialog($event, state, isAdd) { |
||||
|
var prevStateId = null; |
||||
|
if (!isAdd) { |
||||
|
prevStateId = state.id; |
||||
|
} |
||||
|
$mdDialog.show({ |
||||
|
controller: 'DashboardStateDialogController', |
||||
|
controllerAs: 'vm', |
||||
|
templateUrl: dashboardStateDialogTemplate, |
||||
|
parent: angular.element($document[0].body), |
||||
|
locals: {isAdd: isAdd, allStates: vm.allStates, state: angular.copy(state)}, |
||||
|
skipHide: true, |
||||
|
fullscreen: true, |
||||
|
targetEvent: $event |
||||
|
}).then(function (state) { |
||||
|
saveState(state, prevStateId); |
||||
|
updateStates(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function getStateIndex(id) { |
||||
|
var result = $filter('filter')(vm.allStates, {id: id}); |
||||
|
if (result && result.length) { |
||||
|
return vm.allStates.indexOf(result[0]); |
||||
|
} |
||||
|
return -1; |
||||
|
} |
||||
|
|
||||
|
function saveState(state, prevStateId) { |
||||
|
if (prevStateId) { |
||||
|
var index = getStateIndex(prevStateId); |
||||
|
if (index > -1) { |
||||
|
vm.allStates[index] = state; |
||||
|
} |
||||
|
} else { |
||||
|
vm.allStates.push(state); |
||||
|
} |
||||
|
if (state.root) { |
||||
|
for (var i=0; i < vm.allStates.length; i++) { |
||||
|
var otherState = vm.allStates[i]; |
||||
|
if (otherState.id !== state.id) { |
||||
|
otherState.root = false; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
$scope.theForm.$setDirty(); |
||||
|
} |
||||
|
|
||||
|
function deleteState ($event, state) { |
||||
|
if ($event) { |
||||
|
$event.stopPropagation(); |
||||
|
} |
||||
|
if (state) { |
||||
|
var title = $translate.instant('dashboard.delete-state-title'); |
||||
|
var content = $translate.instant('dashboard.delete-state-text', {stateName: state.name}); |
||||
|
var confirm = $mdDialog.confirm() |
||||
|
.targetEvent($event) |
||||
|
.title(title) |
||||
|
.htmlContent(content) |
||||
|
.ariaLabel(title) |
||||
|
.cancel($translate.instant('action.no')) |
||||
|
.ok($translate.instant('action.yes')); |
||||
|
|
||||
|
confirm._options.skipHide = true; |
||||
|
confirm._options.fullscreen = true; |
||||
|
|
||||
|
$mdDialog.show(confirm).then(function () { |
||||
|
var index = getStateIndex(state.id); |
||||
|
if (index > -1) { |
||||
|
vm.allStates.splice(index, 1); |
||||
|
} |
||||
|
$scope.theForm.$setDirty(); |
||||
|
updateStates(); |
||||
|
}); |
||||
|
|
||||
|
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function cancel() { |
||||
|
$mdDialog.cancel(); |
||||
|
} |
||||
|
|
||||
|
function save() { |
||||
|
$scope.theForm.$setPristine(); |
||||
|
var savedStates = {}; |
||||
|
for (var i=0;i<vm.allStates.length;i++) { |
||||
|
var state = vm.allStates[i]; |
||||
|
var id = state.id; |
||||
|
delete state.id; |
||||
|
savedStates[id] = state; |
||||
|
} |
||||
|
$mdDialog.hide(savedStates); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2017 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. |
||||
|
*/ |
||||
|
|
||||
|
.manage-dashboard-states { |
||||
|
table.md-table { |
||||
|
tbody { |
||||
|
tr { |
||||
|
td { |
||||
|
&.tb-action-cell { |
||||
|
overflow: hidden; |
||||
|
text-overflow: ellipsis; |
||||
|
white-space: nowrap; |
||||
|
min-width: 100px; |
||||
|
max-width: 100px; |
||||
|
width: 100px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,127 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2017 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. |
||||
|
|
||||
|
--> |
||||
|
<md-dialog aria-label="{{ 'dashboard.manage-states' | translate }}" style="min-width: 600px;"> |
||||
|
<form name="theForm" ng-submit="vm.save()"> |
||||
|
<md-toolbar> |
||||
|
<div class="md-toolbar-tools"> |
||||
|
<h2 translate>{{ 'dashboard.manage-states' }}</h2> |
||||
|
<span flex></span> |
||||
|
<md-button class="md-icon-button" ng-click="vm.cancel()"> |
||||
|
<ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon> |
||||
|
</md-button> |
||||
|
</div> |
||||
|
</md-toolbar> |
||||
|
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear> |
||||
|
<span style="min-height: 5px;" flex="" ng-show="!loading"></span> |
||||
|
<md-dialog-content> |
||||
|
<div class="md-dialog-content"> |
||||
|
<fieldset ng-disabled="loading"> |
||||
|
<div class="manage-dashboard-states" layout="column"> |
||||
|
<md-toolbar class="md-table-toolbar md-default" ng-show="vm.query.search === null"> |
||||
|
<div class="md-toolbar-tools"> |
||||
|
<span translate>dashboard.states</span> |
||||
|
<span flex></span> |
||||
|
<md-button class="md-icon-button" ng-click="vm.addState($event)"> |
||||
|
<md-icon>add</md-icon> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'dashboard.add-state' | translate }} |
||||
|
</md-tooltip> |
||||
|
</md-button> |
||||
|
<md-button class="md-icon-button" ng-click="vm.enterFilterMode()"> |
||||
|
<md-icon>search</md-icon> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'action.search' | translate }} |
||||
|
</md-tooltip> |
||||
|
</md-button> |
||||
|
</div> |
||||
|
</md-toolbar> |
||||
|
<md-toolbar class="md-table-toolbar md-default" ng-show="vm.query.search != null"> |
||||
|
<div class="md-toolbar-tools"> |
||||
|
<md-button class="md-icon-button" aria-label="{{ 'action.search' | translate }}"> |
||||
|
<md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'dashboard.search-states' | translate }} |
||||
|
</md-tooltip> |
||||
|
</md-button> |
||||
|
<md-input-container md-theme="tb-search-input" flex> |
||||
|
<label> </label> |
||||
|
<input ng-model="vm.query.search" placeholder="{{ 'dashboard.search-states' | translate }}"/> |
||||
|
</md-input-container> |
||||
|
<md-button class="md-icon-button" aria-label="Close" ng-click="vm.exitFilterMode()"> |
||||
|
<md-icon aria-label="Close" class="material-icons">close</md-icon> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'action.close' | translate }} |
||||
|
</md-tooltip> |
||||
|
</md-button> |
||||
|
</div> |
||||
|
</md-toolbar> |
||||
|
<md-table-container> |
||||
|
<table md-table> |
||||
|
<thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder"> |
||||
|
<tr md-row> |
||||
|
<th md-column md-order-by="name"><span translate>dashboard.state-name</span></th> |
||||
|
<th md-column md-order-by="id"><span translate>dashboard.state-id</span></th> |
||||
|
<th md-column md-order-by="root"><span translate>dashboard.is-root-state</span></th> |
||||
|
<th md-column><span> </span></th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody md-body> |
||||
|
<tr md-row md-select="state" ng-disabled="state.root" md-select-id="id" md-auto-select ng-repeat="state in vm.states"> |
||||
|
<td md-cell>{{state.name}}</td> |
||||
|
<td md-cell>{{state.id}}</td> |
||||
|
<td md-cell> |
||||
|
<md-checkbox aria-label="{{'dashboard.is-root-state' | translate }}" |
||||
|
disabled ng-model="state.root"> |
||||
|
</md-checkbox> |
||||
|
</td> |
||||
|
<td md-cell class="tb-action-cell"> |
||||
|
<md-button class="md-icon-button" aria-label="{{ 'action.edit' | translate }}" |
||||
|
ng-click="vm.editState($event, state)"> |
||||
|
<md-icon aria-label="{{ 'action.edit' | translate }}" class="material-icons">edit</md-icon> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'dashboard.edit-state' | translate }} |
||||
|
</md-tooltip> |
||||
|
</md-button> |
||||
|
<md-button ng-show="!state.root" class="md-icon-button" aria-label="Delete" ng-click="vm.deleteState($event, state)"> |
||||
|
<md-icon aria-label="Delete" class="material-icons">delete</md-icon> |
||||
|
<md-tooltip md-direction="top"> |
||||
|
{{ 'dashboard.delete-state' | translate }} |
||||
|
</md-tooltip> |
||||
|
</md-button> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</md-table-container> |
||||
|
<md-table-pagination md-limit="vm.query.limit" md-limit-options="[5, 10, 15]" |
||||
|
md-page="vm.query.page" md-total="{{vm.statesCount}}" |
||||
|
md-on-paginate="vm.onPaginate" md-page-select> |
||||
|
</md-table-pagination> |
||||
|
</div> |
||||
|
</fieldset> |
||||
|
</div> |
||||
|
</md-dialog-content> |
||||
|
<md-dialog-actions layout="row"> |
||||
|
<span flex></span> |
||||
|
<md-button ng-disabled="loading || !theForm.$dirty || !theForm.$valid" type="submit" class="md-raised md-primary"> |
||||
|
{{ 'action.save' | translate }} |
||||
|
</md-button> |
||||
|
<md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }}</md-button> |
||||
|
</md-dialog-actions> |
||||
|
</form> |
||||
|
</md-dialog> |
||||
@ -0,0 +1,35 @@ |
|||||
|
/* |
||||
|
* Copyright © 2016-2017 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. |
||||
|
*/ |
||||
|
|
||||
|
/*@ngInject*/ |
||||
|
export default function SelectTargetStateController($scope, $mdDialog, dashboardUtils, states) { |
||||
|
|
||||
|
var vm = this; |
||||
|
vm.states = states; |
||||
|
vm.stateId = dashboardUtils.getRootStateId(vm.states); |
||||
|
|
||||
|
vm.cancel = cancel; |
||||
|
vm.save = save; |
||||
|
|
||||
|
function cancel() { |
||||
|
$mdDialog.cancel(); |
||||
|
} |
||||
|
|
||||
|
function save() { |
||||
|
$scope.theForm.$setPristine(); |
||||
|
$mdDialog.hide(vm.stateId); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,50 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2017 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. |
||||
|
|
||||
|
--> |
||||
|
<md-dialog aria-label="{{ 'dashboard.select-state' | translate }}"> |
||||
|
<form name="theForm" ng-submit="vm.save()"> |
||||
|
<md-toolbar> |
||||
|
<div class="md-toolbar-tools"> |
||||
|
<h2 translate>{{ 'dashboard.select-state' }}</h2> |
||||
|
<span flex></span> |
||||
|
<md-button class="md-icon-button" ng-click="vm.cancel()"> |
||||
|
<ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon> |
||||
|
</md-button> |
||||
|
</div> |
||||
|
</md-toolbar> |
||||
|
<md-progress-linear class="md-warn" md-mode="indeterminate" ng-disabled="!loading" ng-show="loading"></md-progress-linear> |
||||
|
<span style="min-height: 5px;" flex="" ng-show="!loading"></span> |
||||
|
<md-dialog-content> |
||||
|
<div class="md-dialog-content"> |
||||
|
<fieldset ng-disabled="loading"> |
||||
|
<md-select required aria-label="{{ 'dashboard.state' | translate }}" ng-model="vm.stateId"> |
||||
|
<md-option ng-repeat="(stateId, state) in vm.states" ng-value="stateId"> |
||||
|
{{state.name}} |
||||
|
</md-option> |
||||
|
</md-select> |
||||
|
</fieldset> |
||||
|
</div> |
||||
|
</md-dialog-content> |
||||
|
<md-dialog-actions layout="row"> |
||||
|
<span flex></span> |
||||
|
<md-button ng-disabled="loading || !theForm.$dirty || !theForm.$valid" type="submit" class="md-raised md-primary"> |
||||
|
{{ 'action.save' | translate }} |
||||
|
</md-button> |
||||
|
<md-button ng-disabled="loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }}</md-button> |
||||
|
</md-dialog-actions> |
||||
|
</form> |
||||
|
</md-dialog> |
||||
@ -0,0 +1,117 @@ |
|||||
|
/* |
||||
|
* Copyright © 2016-2017 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. |
||||
|
*/ |
||||
|
|
||||
|
/*@ngInject*/ |
||||
|
export default function StatesComponent($compile, $templateCache, $controller, statesControllerService) { |
||||
|
|
||||
|
var linker = function (scope, element) { |
||||
|
|
||||
|
function destroyStateController() { |
||||
|
if (scope.statesController && angular.isFunction(scope.statesController.$onDestroy)) { |
||||
|
scope.statesController.$onDestroy(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function init() { |
||||
|
|
||||
|
var stateController = scope.dashboardCtrl.dashboardCtx.stateController; |
||||
|
|
||||
|
stateController.openState = function(id, params) { |
||||
|
if (scope.statesController) { |
||||
|
scope.statesController.openState(id, params); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
stateController.updateState = function(id, params) { |
||||
|
if (scope.statesController) { |
||||
|
scope.statesController.updateState(id, params); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
stateController.navigatePrevState = function(index) { |
||||
|
if (scope.statesController) { |
||||
|
scope.statesController.navigatePrevState(index); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
stateController.getStateId = function() { |
||||
|
if (scope.statesController) { |
||||
|
return scope.statesController.getStateId(); |
||||
|
} else { |
||||
|
return ''; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
stateController.getStateParams = function() { |
||||
|
if (scope.statesController) { |
||||
|
return scope.statesController.getStateParams(); |
||||
|
} else { |
||||
|
return {}; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
scope.$on('$destroy', function callOnDestroyHook() { |
||||
|
destroyStateController(); |
||||
|
}); |
||||
|
|
||||
|
scope.$watch('scope.dashboardCtrl', function() { |
||||
|
if (scope.dashboardCtrl.dashboardCtx) { |
||||
|
init(); |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
scope.$watch('statesControllerId', function(newValue) { |
||||
|
if (newValue) { |
||||
|
if (scope.statesController) { |
||||
|
destroyStateController(); |
||||
|
} |
||||
|
var statesControllerInfo = statesControllerService.getStateController(scope.statesControllerId); |
||||
|
if (!statesControllerInfo) { |
||||
|
//fallback to default
|
||||
|
statesControllerInfo = statesControllerService.getStateController('default'); |
||||
|
} |
||||
|
var template = $templateCache.get(statesControllerInfo.templateUrl); |
||||
|
element.html(template); |
||||
|
var locals = {}; |
||||
|
angular.extend(locals, {$scope: scope, $element: element}); |
||||
|
var controller = $controller(statesControllerInfo.controller, locals, true, 'vm'); |
||||
|
controller.instance = controller(); |
||||
|
scope.statesController = controller.instance; |
||||
|
scope.statesController.dashboardCtrl = scope.dashboardCtrl; |
||||
|
scope.statesController.states = scope.states; |
||||
|
$compile(element.contents())(scope); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
scope.$watch('states', function() { |
||||
|
if (scope.statesController) { |
||||
|
scope.statesController.states = scope.states; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
restrict: "E", |
||||
|
link: linker, |
||||
|
scope: { |
||||
|
statesControllerId: '=', |
||||
|
dashboardCtrl: '=', |
||||
|
states: '=' |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
@ -0,0 +1,60 @@ |
|||||
|
/* |
||||
|
* Copyright © 2016-2017 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. |
||||
|
*/ |
||||
|
|
||||
|
/* eslint-disable import/no-unresolved, import/default */ |
||||
|
|
||||
|
import defaultStateControllerTemplate from './default-state-controller.tpl.html'; |
||||
|
import entityStateControllerTemplate from './entity-state-controller.tpl.html'; |
||||
|
|
||||
|
/* eslint-enable import/no-unresolved, import/default */ |
||||
|
|
||||
|
import DefaultStateController from './default-state-controller'; |
||||
|
import EntityStateController from './entity-state-controller'; |
||||
|
|
||||
|
/*@ngInject*/ |
||||
|
export default function StatesControllerService() { |
||||
|
|
||||
|
var statesControllers = {}; |
||||
|
statesControllers['default'] = { |
||||
|
controller: DefaultStateController, |
||||
|
templateUrl: defaultStateControllerTemplate |
||||
|
}; |
||||
|
statesControllers['entity'] = { |
||||
|
controller: EntityStateController, |
||||
|
templateUrl: entityStateControllerTemplate |
||||
|
}; |
||||
|
|
||||
|
var service = { |
||||
|
registerStatesController: registerStatesController, |
||||
|
getStateControllers: getStateControllers, |
||||
|
getStateController: getStateController |
||||
|
}; |
||||
|
|
||||
|
return service; |
||||
|
|
||||
|
function registerStatesController(id, stateControllerInfo) { |
||||
|
statesControllers[id] = stateControllerInfo; |
||||
|
} |
||||
|
|
||||
|
function getStateControllers() { |
||||
|
return statesControllers; |
||||
|
} |
||||
|
|
||||
|
function getStateController(id) { |
||||
|
return statesControllers[id]; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
/* |
||||
|
* Copyright © 2016-2017 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. |
||||
|
*/ |
||||
|
|
||||
|
/*@ngInject*/ |
||||
|
export default function ThingsboardMissingTranslateHandler($log, types) { |
||||
|
|
||||
|
return function (translationId) { |
||||
|
if (translationId && !translationId.startsWith(types.translate.dashboardStatePrefix)) { |
||||
|
$log.warn('Translation for ' + translationId + ' doesn\'t exist'); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
} |
||||
Loading…
Reference in new issue