diff --git a/application/src/main/java/com/hashmapinc/server/controller/TemplateController.java b/application/src/main/java/com/hashmapinc/server/controller/TemplateController.java new file mode 100644 index 000000000..1966b9875 --- /dev/null +++ b/application/src/main/java/com/hashmapinc/server/controller/TemplateController.java @@ -0,0 +1,113 @@ +/** + * Copyright © 2016-2018 The Thingsboard Authors + * Modifications © 2017-2018 Hashmap, Inc + * + * 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 com.hashmapinc.server.controller; + +import com.hashmapinc.server.common.data.id.TemplateId; +import com.hashmapinc.server.common.data.page.PaginatedResult; +import com.hashmapinc.server.common.data.page.TextPageData; +import com.hashmapinc.server.common.data.page.TextPageLink; +import com.hashmapinc.server.common.data.template.TemplateMetadata; +import com.hashmapinc.server.dao.template.TemplateService; +import com.hashmapinc.server.common.data.exception.TempusException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api") +@Slf4j +public class TemplateController extends BaseController { + + @Autowired + private TemplateService templateService; + + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @PostMapping(value = "/template", produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public TemplateMetadata save(@RequestBody TemplateMetadata source) throws TempusException { + try { + return templateService.save(source); + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @DeleteMapping(value = "/template/{id}") + public void delete(@PathVariable String id) throws TempusException { + try { + templateService.delete(new TemplateId(toUUID(id))); + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @GetMapping(value = "/template/{id}", produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public TemplateMetadata get(@PathVariable String id) throws TempusException { + try { + return templateService.getTemplate(new TemplateId(toUUID(id))); + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @GetMapping(value = "/templates", produces = MediaType.APPLICATION_JSON_VALUE, params = "limit") + @ResponseBody + public TextPageData getTemplates(@RequestParam int limit, + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String idOffset, + @RequestParam(required = false) String textOffset) throws TempusException { + try { + TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); + return templateService.getTemplate(pageLink); + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @GetMapping(value = "/templates", produces = MediaType.APPLICATION_JSON_VALUE, params = {"limit", "pageNum"}) + @ResponseBody + public PaginatedResult getTemplatesByPage(@RequestParam int limit, + @RequestParam(required = false) int pageNum, + @RequestParam(required = false) String textSearch) throws TempusException { + try { + return templateService.getTemplatesByPage(limit, pageNum, textSearch); + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @GetMapping(value = "/templates", produces = MediaType.APPLICATION_JSON_VALUE) + @ResponseBody + public List getAll() throws TempusException { + try { + return templateService.getAllTemplates(); + } catch (Exception e) { + throw handleException(e); + } + } + +} diff --git a/common/data/src/main/java/com/hashmapinc/server/common/data/EntityType.java b/common/data/src/main/java/com/hashmapinc/server/common/data/EntityType.java index b6bb50611..6be146a87 100644 --- a/common/data/src/main/java/com/hashmapinc/server/common/data/EntityType.java +++ b/common/data/src/main/java/com/hashmapinc/server/common/data/EntityType.java @@ -18,6 +18,6 @@ public enum EntityType { - TENANT, CUSTOMER, USER, RULE, PLUGIN, DASHBOARD, ASSET, DEVICE, ALARM, COMPUTATION, COMPUTATION_JOB, NODE_METRIC,THEME, LOGO, - DATA_MODEL_OBJECT, DATA_MODEL, CUSTOMER_GROUP, TEMPUS_GATEWAY_CONFIGURATION + TENANT, CUSTOMER, USER, RULE, PLUGIN, DASHBOARD, ASSET, DEVICE, ALARM, COMPUTATION, COMPUTATION_JOB, NODE_METRIC, THEME, LOGO, + DATA_MODEL_OBJECT, DATA_MODEL, CUSTOMER_GROUP, TEMPUS_GATEWAY_CONFIGURATION, TEMPLATE } diff --git a/common/data/src/main/java/com/hashmapinc/server/common/data/id/EntityIdFactory.java b/common/data/src/main/java/com/hashmapinc/server/common/data/id/EntityIdFactory.java index fa53c5d3f..a02fb7f5e 100644 --- a/common/data/src/main/java/com/hashmapinc/server/common/data/id/EntityIdFactory.java +++ b/common/data/src/main/java/com/hashmapinc/server/common/data/id/EntityIdFactory.java @@ -74,6 +74,8 @@ public static EntityId getByTypeAndUuid(EntityType type, UUID uuid) { return new CustomerGroupId(uuid); case TEMPUS_GATEWAY_CONFIGURATION: return new TempusGatewayConfigurationId(uuid); + case TEMPLATE: + return new TemplateId(uuid); } throw new IllegalArgumentException("EntityType " + type + " is not supported!"); } diff --git a/common/data/src/main/java/com/hashmapinc/server/common/data/id/TemplateId.java b/common/data/src/main/java/com/hashmapinc/server/common/data/id/TemplateId.java new file mode 100644 index 000000000..41df60185 --- /dev/null +++ b/common/data/src/main/java/com/hashmapinc/server/common/data/id/TemplateId.java @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2018 The Thingsboard Authors + * Modifications © 2017-2018 Hashmap, Inc + * + * 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 com.hashmapinc.server.common.data.id; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.hashmapinc.server.common.data.EntityType; + +import java.util.UUID; + +public class TemplateId extends UUIDBased implements EntityId { + + @JsonCreator + public TemplateId(@JsonProperty("id") UUID id) { + super(id); + } + + @Override + public EntityType getEntityType() { + return EntityType.TEMPLATE; + } +} diff --git a/common/data/src/main/java/com/hashmapinc/server/common/data/template/TemplateMetadata.java b/common/data/src/main/java/com/hashmapinc/server/common/data/template/TemplateMetadata.java new file mode 100644 index 000000000..28d35f93d --- /dev/null +++ b/common/data/src/main/java/com/hashmapinc/server/common/data/template/TemplateMetadata.java @@ -0,0 +1,47 @@ +/** + * Copyright © 2016-2018 The Thingsboard Authors + * Modifications © 2017-2018 Hashmap, Inc + * + * 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 com.hashmapinc.server.common.data.template; + +import com.hashmapinc.server.common.data.HasName; +import com.hashmapinc.server.common.data.SearchTextBased; +import com.hashmapinc.server.common.data.id.TemplateId; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Data +@NoArgsConstructor +public class TemplateMetadata extends SearchTextBased implements HasName { + + private String name; + private String body; + + public TemplateMetadata(TemplateId id) { + super(id); + } + + @Override + public String getName() { + return name; + } + + @Override + public String getSearchText() { + return name; + } +} diff --git a/dao/src/it/java/com/hashmapinc/server/dao/service/AbstractServiceTest.java b/dao/src/it/java/com/hashmapinc/server/dao/service/AbstractServiceTest.java index f27f56304..008f4a8ec 100644 --- a/dao/src/it/java/com/hashmapinc/server/dao/service/AbstractServiceTest.java +++ b/dao/src/it/java/com/hashmapinc/server/dao/service/AbstractServiceTest.java @@ -66,6 +66,7 @@ import com.hashmapinc.server.dao.relation.RelationService; import com.hashmapinc.server.dao.rule.RuleService; import com.hashmapinc.server.dao.settings.UserSettingsService; +import com.hashmapinc.server.dao.template.TemplateService; import com.hashmapinc.server.dao.tenant.TenantService; import com.hashmapinc.server.dao.theme.ThemeService; import com.hashmapinc.server.dao.timeseries.TimeseriesService; @@ -147,6 +148,9 @@ public abstract class AbstractServiceTest { @Autowired protected PluginService pluginService; + @Autowired + protected TemplateService templateService; + @Autowired protected RuleService ruleService; diff --git a/dao/src/it/java/com/hashmapinc/server/dao/service/template/BaseTemplateServiceTest.java b/dao/src/it/java/com/hashmapinc/server/dao/service/template/BaseTemplateServiceTest.java new file mode 100644 index 000000000..5d43ecf17 --- /dev/null +++ b/dao/src/it/java/com/hashmapinc/server/dao/service/template/BaseTemplateServiceTest.java @@ -0,0 +1,79 @@ +/** + * Copyright © 2016-2018 The Thingsboard Authors + * Modifications © 2017-2018 Hashmap, Inc + * + * 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 com.hashmapinc.server.dao.service.template; + +import com.hashmapinc.server.common.data.page.PaginatedResult; +import com.hashmapinc.server.common.data.template.TemplateMetadata; +import com.hashmapinc.server.dao.service.AbstractServiceTest; +import org.junit.Assert; +import org.junit.Test; + +import java.util.List; + +public abstract class BaseTemplateServiceTest extends AbstractServiceTest { + + @Test + public void saveTemplate() { + TemplateMetadata givenTemplateMetadata = getTemplateMetadata("1"); + TemplateMetadata savedTemplateMetaData = templateService.save(givenTemplateMetadata); + Assert.assertNotNull(savedTemplateMetaData.getId()); + + TemplateMetadata fetchedTemplateMetadata = templateService.getTemplate(savedTemplateMetaData.getId()); + Assert.assertEquals(givenTemplateMetadata.getName(), fetchedTemplateMetadata.getName()); + Assert.assertEquals(givenTemplateMetadata.getBody(), fetchedTemplateMetadata.getBody()); + + templateService.delete(savedTemplateMetaData.getId()); + } + + @Test + public void deleteTemplate() { + TemplateMetadata givenTemplateMetadata = getTemplateMetadata("1"); + TemplateMetadata savedTemplateMetadata = templateService.save(givenTemplateMetadata); + Assert.assertNotNull(savedTemplateMetadata.getId()); + + templateService.delete(savedTemplateMetadata.getId()); + TemplateMetadata templateMetadata = templateService.getTemplate(savedTemplateMetadata.getId()); + Assert.assertNull(templateMetadata); + } + + @Test + public void getTemplates() { + TemplateMetadata givenTemplateMetadata1 = getTemplateMetadata("1"); + TemplateMetadata savedTemplateMetadata1 = templateService.save(givenTemplateMetadata1); + Assert.assertNotNull(savedTemplateMetadata1.getId()); + TemplateMetadata givenTemplateMetadata2 = getTemplateMetadata("2"); + TemplateMetadata savedTemplateMetadata2 = templateService.save(givenTemplateMetadata2); + Assert.assertNotNull(savedTemplateMetadata2.getId()); + TemplateMetadata givenTemplateMetadata3 = getTemplateMetadata("3"); + TemplateMetadata savedTemplateMetadata3 = templateService.save(givenTemplateMetadata3); + Assert.assertNotNull(savedTemplateMetadata3.getId()); + + List templateMetadataList = templateService.getAllTemplates(); + Assert.assertEquals(3, templateMetadataList.size()); + + PaginatedResult templateMetadataPage = templateService.getTemplatesByPage(2, 1, "temp"); + Assert.assertEquals(1, templateMetadataPage.getData().size()); + } + + private TemplateMetadata getTemplateMetadata(String suffix) { + TemplateMetadata templateMetadata = new TemplateMetadata(); + templateMetadata.setName("templatename" + suffix); + templateMetadata.setBody("{templatebody}"); + return templateMetadata; + } + +} diff --git a/dao/src/it/java/com/hashmapinc/server/dao/service/template/sql/TemplateServiceSqlTest.java b/dao/src/it/java/com/hashmapinc/server/dao/service/template/sql/TemplateServiceSqlTest.java new file mode 100644 index 000000000..b4f571e0f --- /dev/null +++ b/dao/src/it/java/com/hashmapinc/server/dao/service/template/sql/TemplateServiceSqlTest.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2018 The Thingsboard Authors + * Modifications © 2017-2018 Hashmap, Inc + * + * 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 com.hashmapinc.server.dao.service.template.sql; + +import com.hashmapinc.server.dao.service.DaoSqlTest; +import com.hashmapinc.server.dao.service.template.BaseTemplateServiceTest; + +@DaoSqlTest +public class TemplateServiceSqlTest extends BaseTemplateServiceTest { +} diff --git a/dao/src/it/resources/sql/drop-all-tables.sql b/dao/src/it/resources/sql/drop-all-tables.sql index 43e91265b..c3a3fc8e3 100644 --- a/dao/src/it/resources/sql/drop-all-tables.sql +++ b/dao/src/it/resources/sql/drop-all-tables.sql @@ -38,4 +38,5 @@ DROP TABLE IF EXISTS kubeless_computation_meta_data; DROP TABLE IF EXISTS metadata_entries; DROP TABLE IF EXISTS spark_computation_meta_data; DROP TABLE IF EXISTS tempus_gateway_configuration; -DROP TABLE IF EXISTS tenant_unit_system; \ No newline at end of file +DROP TABLE IF EXISTS tenant_unit_system; +DROP TABLE IF EXISTS templates; \ No newline at end of file diff --git a/dao/src/main/java/com/hashmapinc/server/dao/model/ModelConstants.java b/dao/src/main/java/com/hashmapinc/server/dao/model/ModelConstants.java index c95f3124b..df7deb623 100644 --- a/dao/src/main/java/com/hashmapinc/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/com/hashmapinc/server/dao/model/ModelConstants.java @@ -331,6 +331,13 @@ private ModelConstants() { public static final String RULE_PROCESSOR = "processor"; public static final String RULE_ACTION = "action"; + /** + * Template metadata constants. + */ + public static final String TEMPLATE_COLUMN_FAMILY_NAME = "templates"; + public static final String TEMPLATE_NAME_PROPERTY = "name"; + public static final String TEMPLATE_BODY_PROPERTY = "body"; + /** * Event constants. */ diff --git a/dao/src/main/java/com/hashmapinc/server/dao/model/sql/TemplateMetadataEntity.java b/dao/src/main/java/com/hashmapinc/server/dao/model/sql/TemplateMetadataEntity.java new file mode 100644 index 000000000..7640c5d3c --- /dev/null +++ b/dao/src/main/java/com/hashmapinc/server/dao/model/sql/TemplateMetadataEntity.java @@ -0,0 +1,72 @@ +/** + * Copyright © 2016-2018 The Thingsboard Authors + * Modifications © 2017-2018 Hashmap, Inc + * + * 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 com.hashmapinc.server.dao.model.sql; + +import com.datastax.driver.core.utils.UUIDs; +import com.hashmapinc.server.common.data.id.TemplateId; +import com.hashmapinc.server.common.data.template.TemplateMetadata; +import com.hashmapinc.server.dao.model.BaseSqlEntity; +import com.hashmapinc.server.dao.model.ModelConstants; +import com.hashmapinc.server.dao.model.SearchTextEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +@Data +@NoArgsConstructor +@EqualsAndHashCode(callSuper = true) +@Entity +@Table(name = ModelConstants.TEMPLATE_COLUMN_FAMILY_NAME) +public class TemplateMetadataEntity extends BaseSqlEntity implements SearchTextEntity { + + @Column(name = ModelConstants.TEMPLATE_NAME_PROPERTY) + private String name; + + @Column(name = ModelConstants.TEMPLATE_BODY_PROPERTY) + private String body; + + public TemplateMetadataEntity(TemplateMetadata templateMetadata) { + if (templateMetadata.getId() != null) { + this.setId(templateMetadata.getId().getId()); + } + this.name = templateMetadata.getName(); + this.body = templateMetadata.getBody(); + } + + @Override + public String getSearchTextSource() { + return name; + } + + @Override + public void setSearchText(String searchText) { + this.name = searchText; + } + + @Override + public TemplateMetadata toData() { + TemplateMetadata templateMetadata = new TemplateMetadata(new TemplateId(getId())); + templateMetadata.setName(name); + templateMetadata.setBody(body); + templateMetadata.setCreatedTime(UUIDs.unixTimestamp(getId())); + return templateMetadata; + } +} diff --git a/dao/src/main/java/com/hashmapinc/server/dao/sql/template/JpaBaseTemplateDao.java b/dao/src/main/java/com/hashmapinc/server/dao/sql/template/JpaBaseTemplateDao.java new file mode 100644 index 000000000..8c6ad1e4c --- /dev/null +++ b/dao/src/main/java/com/hashmapinc/server/dao/sql/template/JpaBaseTemplateDao.java @@ -0,0 +1,90 @@ +/** + * Copyright © 2016-2018 The Thingsboard Authors + * Modifications © 2017-2018 Hashmap, Inc + * + * 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 com.hashmapinc.server.dao.sql.template; + +import com.hashmapinc.server.common.data.UUIDConverter; +import com.hashmapinc.server.common.data.page.PaginatedResult; +import com.hashmapinc.server.common.data.page.TextPageLink; +import com.hashmapinc.server.common.data.template.TemplateMetadata; +import com.hashmapinc.server.dao.DaoUtil; +import com.hashmapinc.server.dao.exception.DataValidationException; +import com.hashmapinc.server.dao.model.ModelConstants; +import com.hashmapinc.server.dao.model.sql.TemplateMetadataEntity; +import com.hashmapinc.server.dao.sql.JpaAbstractSearchTextDao; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.StreamSupport; + +@Component +public class JpaBaseTemplateDao extends JpaAbstractSearchTextDao { + + @Autowired + private TemplateRepository templateRepository; + + @Override + protected Class getEntityClass() { + return TemplateMetadataEntity.class; + } + + @Override + protected CrudRepository getCrudRepository() { + return templateRepository; + } + + @Override + public TemplateMetadata save(TemplateMetadata templateMetadata) { + validateForDuplicateName(templateMetadata); + return super.save(templateMetadata); + } + + public List findByPageLink(TextPageLink pageLink) { + return DaoUtil.convertDataList( + templateRepository.findTemplate(Objects.toString(pageLink.getTextSearch(), ""), + pageLink.getIdOffset() == null ? ModelConstants.NULL_UUID_STR : UUIDConverter.fromTimeUUID(pageLink.getIdOffset()), + PageRequest.of(0, pageLink.getLimit()))); + + } + + public PaginatedResult findByPageNumber(int limit, int pageNum, String searchText) { + PageRequest pageable = PageRequest.of(pageNum, limit, Sort.by(new Sort.Order(Sort.Direction.ASC, ModelConstants.ID_PROPERTY))); + Page resultPage = templateRepository.findAll(searchText, pageable); + if(resultPage == null) { + return new PaginatedResult<>(Collections.emptyList(), pageNum, 0, 0, false, false); + } + + List templates = DaoUtil.convertDataList(resultPage.getContent()); + return new PaginatedResult<>(templates, pageNum, resultPage.getTotalElements(), + resultPage.getTotalPages(), resultPage.hasNext(), resultPage.hasPrevious()); + } + + private void validateForDuplicateName(TemplateMetadata templateMetadata) { + boolean duplicateNamePresent = StreamSupport.stream(templateRepository.findAll().spliterator(), false) + .map(TemplateMetadataEntity::getName) + .anyMatch(t -> Objects.equals(t, templateMetadata.getName())); + if(duplicateNamePresent) { + throw new DataValidationException("Name already present! Provide a different name"); + } + } +} diff --git a/dao/src/main/java/com/hashmapinc/server/dao/sql/template/TemplateRepository.java b/dao/src/main/java/com/hashmapinc/server/dao/sql/template/TemplateRepository.java new file mode 100644 index 000000000..e51a9c689 --- /dev/null +++ b/dao/src/main/java/com/hashmapinc/server/dao/sql/template/TemplateRepository.java @@ -0,0 +1,42 @@ +/** + * Copyright © 2016-2018 The Thingsboard Authors + * Modifications © 2017-2018 Hashmap, Inc + * + * 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 com.hashmapinc.server.dao.sql.template; + +import com.hashmapinc.server.dao.model.sql.TemplateMetadataEntity; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface TemplateRepository extends CrudRepository { + + List findByName(String name); + + @Query("SELECT t FROM TemplateMetadataEntity t WHERE LOWER(t.name) LIKE LOWER(CONCAT(:searchText, '%')) AND t.id > :idOffset ORDER BY t.id") + List findTemplate(@Param("searchText") String searchText, + @Param("idOffset") String idOffset, + Pageable pageable); + + @Query(value = "SELECT t from TemplateMetadataEntity t WHERE LOWER(t.name) LIKE LOWER(CONCAT(:searchText, '%')) ORDER BY t.id", + countQuery = "SELECT count(*) FROM TemplateMetadataEntity") + Page findAll(@Param("searchText") String searchText, Pageable pageable); +} diff --git a/dao/src/main/java/com/hashmapinc/server/dao/template/TemplateService.java b/dao/src/main/java/com/hashmapinc/server/dao/template/TemplateService.java new file mode 100644 index 000000000..5e48fef1f --- /dev/null +++ b/dao/src/main/java/com/hashmapinc/server/dao/template/TemplateService.java @@ -0,0 +1,62 @@ +/** + * Copyright © 2016-2018 The Thingsboard Authors + * Modifications © 2017-2018 Hashmap, Inc + * + * 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 com.hashmapinc.server.dao.template; + +import com.hashmapinc.server.common.data.id.TemplateId; +import com.hashmapinc.server.common.data.page.PaginatedResult; +import com.hashmapinc.server.common.data.page.TextPageData; +import com.hashmapinc.server.common.data.page.TextPageLink; +import com.hashmapinc.server.common.data.template.TemplateMetadata; +import com.hashmapinc.server.dao.sql.template.JpaBaseTemplateDao; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@Slf4j +public class TemplateService { + + @Autowired + private JpaBaseTemplateDao dao; + + public TemplateMetadata getTemplate(TemplateId id) { + return dao.findById(id.getId()); + } + + public TextPageData getTemplate(TextPageLink pageLink) { + return new TextPageData<>(dao.findByPageLink(pageLink), pageLink); + } + + public List getAllTemplates() { + return dao.find(); + } + + public TemplateMetadata save(TemplateMetadata templateMetadata) { + return dao.save(templateMetadata); + } + + public void delete(TemplateId id) { + dao.removeById(id.getId()); + } + + + public PaginatedResult getTemplatesByPage(int limit, int pageNum, String textSearch) { + return dao.findByPageNumber(limit, pageNum, textSearch); + } +} diff --git a/dao/src/main/resources/sql/hsql/schema.sql b/dao/src/main/resources/sql/hsql/schema.sql index 022222e7e..8347ef752 100644 --- a/dao/src/main/resources/sql/hsql/schema.sql +++ b/dao/src/main/resources/sql/hsql/schema.sql @@ -419,3 +419,9 @@ CREATE TABLE IF NOT EXISTS tenant_unit_system ( tenant_id varchar(31) NOT NULL CONSTRAINT tenant_unit_system_pkey PRIMARY KEY, unit_system varchar ); + +CREATE TABLE IF NOT EXISTS templates ( + id varchar(31) NOT NULL CONSTRAINT templates_pkey PRIMARY KEY, + name varchar, + body varchar +); diff --git a/dao/src/main/resources/sql/postgres/schema.sql b/dao/src/main/resources/sql/postgres/schema.sql index 9cf79006d..c392b4d1d 100644 --- a/dao/src/main/resources/sql/postgres/schema.sql +++ b/dao/src/main/resources/sql/postgres/schema.sql @@ -427,4 +427,10 @@ CREATE TABLE IF NOT EXISTS kubeless_computation_meta_data ( CREATE TABLE IF NOT EXISTS tenant_unit_system ( tenant_id varchar(31) NOT NULL CONSTRAINT tenant_unit_system_pkey PRIMARY KEY, unit_system varchar +); + +CREATE TABLE IF NOT EXISTS templates ( + id varchar(31) NOT NULL CONSTRAINT templates_pkey PRIMARY KEY, + name varchar, + body varchar ); \ No newline at end of file diff --git a/ui/src/app/api/template.service.js b/ui/src/app/api/template.service.js new file mode 100644 index 000000000..a5ad1d391 --- /dev/null +++ b/ui/src/app/api/template.service.js @@ -0,0 +1,96 @@ +/* + * Copyright © 2016-2018 The Thingsboard Authors + * Modifications © 2017-2018 Hashmap, Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export default angular.module('tempus.api.template', []) + .factory('templateService', TemplateService) + .name; + +/*@ngInject*/ +function TemplateService($q, $http) { + + + var service = { + saveTemplate: saveTemplate, + getTemplates: getTemplates, + deleteTemplate: deleteTemplate, + getAllTemplates: getAllTemplates + } + + return service; + + function saveTemplate(item) { + + var deferred = $q.defer(); + var url = '/api/template'; + $http.post(url, item).then(function success(response) { + deferred.resolve(response.data); + }, function fail() { + deferred.reject(); + }); + return deferred.promise; + } + + + function getTemplates(pageLink, pageNum) { + + var deferred = $q.defer(); + var url = '/api/templates?limit=' + pageLink.limit + '&pageNum=' + pageNum; + if (angular.isDefined(pageLink.textSearch)) { + url += '&textSearch=' + pageLink.textSearch; + } + if (angular.isDefined(pageLink.idOffset)) { + url += '&idOffset=' + pageLink.idOffset; + } + if (angular.isDefined(pageLink.textOffset)) { + url += '&textOffset=' + pageLink.textOffset; + } + $http.get(url, null).then(function success(response) { + deferred.resolve(response.data); + }, function fail() { + deferred.reject(); + }); + return deferred.promise; + } + + function deleteTemplate(templateId){ + + var deferred = $q.defer(); + var url = '/api/template/'+templateId; + $http.delete(url).then(function success() { + deferred.resolve(); + }, function fail() { + deferred.reject(); + }); + return deferred.promise; + + + } + + function getAllTemplates(){ + + var deferred = $q.defer(); + var url = '/api/templates'; + $http.get(url, null).then(function success(response) { + deferred.resolve(response.data); + }, function fail() { + deferred.reject(); + }); + return deferred.promise; + } + + + +} diff --git a/ui/src/app/app.js b/ui/src/app/app.js index d7ae7f336..3c48f7cf3 100644 --- a/ui/src/app/app.js +++ b/ui/src/app/app.js @@ -79,10 +79,12 @@ import tempusClusterInfo from './api/cluster-info.service'; import tempusApiComputation from './api/computation.service'; import tempusApiComputationJob from './api/computation-job.service'; import tempusDataModels from './data_models'; +import tempusTemplates from './templateeditor'; import tempusgateway from './tempusgateway'; import tempusMetadata from './metadata'; import tempusApiAuditLog from './api/audit-log.service'; + import 'typeface-roboto'; import 'font-awesome/css/font-awesome.min.css'; import 'angular-material/angular-material.min.css'; @@ -152,6 +154,7 @@ angular.module('tempus', [ tempusgateway, tempusMetadata, tempusApiAuditLog, + tempusTemplates, uiRouter]) .config(AppConfig) .factory('globalInterceptor', GlobalInterceptor) diff --git a/ui/src/app/components/grid.directive.js b/ui/src/app/components/grid.directive.js index 7be4289e9..dc6c18abf 100755 --- a/ui/src/app/components/grid.directive.js +++ b/ui/src/app/components/grid.directive.js @@ -538,7 +538,13 @@ function GridController($scope, $rootScope, $state, $mdDialog, $document, $q, $m vm.clickItemFunc(data[0],data[1]); }); + var gridTableTemplate = $rootScope.$on("CallTableDetailTemplate", function($event, data){ + vm.clickItemFunc(data[0],data[1]); + }); + + $scope.$on('$destroy', gridTableDevice); + $scope.$on('$destroy', gridTableTemplate); vm.onGridInited(vm); @@ -628,6 +634,7 @@ function GridController($scope, $rootScope, $state, $mdDialog, $document, $q, $m vm.detailsConfig.currentItem = detailsItem; vm.detailsConfig.isDetailsEditMode = false; vm.detailsConfig.isDetailsOpen = true; + }); } @@ -706,14 +713,15 @@ function GridController($scope, $rootScope, $state, $mdDialog, $document, $q, $m vm.parentCtl.loadTableData(); } var index = vm.detailsConfig.currentItem.index; - item.index = index; - vm.detailsConfig.currentItem = item; - vm.items.data[index] = item; - var row = Math.floor(index / vm.columns); - var itemRow = vm.items.rowData[row]; - var column = index % vm.columns; - itemRow[column] = item; - + if(angular.isDefined(index)) { + item.index = index; + vm.detailsConfig.currentItem = item; + vm.items.data[index] = item; + var row = Math.floor(index / vm.columns); + var itemRow = vm.items.rowData[row]; + var column = index % vm.columns; + itemRow[column] = item; + } }); } diff --git a/ui/src/app/components/grid.tpl.html b/ui/src/app/components/grid.tpl.html index bbb7b788e..766efaeb1 100644 --- a/ui/src/app/components/grid.tpl.html +++ b/ui/src/app/components/grid.tpl.html @@ -17,8 +17,8 @@ --> {{vm.noItemsText()}}
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js index 8075ffabe..f8eaaf33a 100644 --- a/ui/src/app/locale/locale.constant.js +++ b/ui/src/app/locale/locale.constant.js @@ -342,7 +342,33 @@ export default angular.module('tempus.locale', []) "total-replica":"Total Replica", "ready":"Ready", "in-progress":"In Progress", - "crash":"Crashed" + "crash":"Crashed", + "delete":"" + + }, + "templateEditor" :{ + + "templateEditor":"Templates", + "title":"Templates", + "add-template-text": "Add New Template", + "name":"Name", + "templateBody":"Template Body", + "name-required":"Name is required", + "grid":"Grid", + "table":"Tabular", + "delete":"Delete Template", + "copy":"Copy Template", + "templateDetails":"Template Details", + "action":"Actions", + "body-required":"Template Body is Required", + "delete-template-title": "Are you sure you want to delete the template '{{template}}'?", + "delete-template-text": "Be careful, after the confirmation the group and all related data will become unrecoverable.", + "delete-templates-title":"Are you sure you want to delete { count, select, 1 {1 template} other {# templates} }?", + "delete-templates-action-title":"Delete { count, select, 1 {1 template} other {# templates} }", + "delete-templates-text": "Be careful, after the confirmation all selected templates will be removed.", + "no-template-text":"No Templates Available", + "createdDate":"Created Date" + }, "audit-log": { diff --git a/ui/src/app/services/menu.service.js b/ui/src/app/services/menu.service.js index 78b1a9ceb..50cce0850 100644 --- a/ui/src/app/services/menu.service.js +++ b/ui/src/app/services/menu.service.js @@ -58,7 +58,7 @@ function Menu(userService, $state, $rootScope, $log,datamodelService,customerSer function getGeneratedSectionTree() { return generatedSectionTree; } - + function getHomeSections() { return homeSections; } @@ -296,6 +296,15 @@ function Menu(userService, $state, $rootScope, $log,datamodelService,customerSer icon: 'dashboards', link: '/static/svg/computationslightgray.svg' }, + + { + name: 'templateEditor.templateEditor', + type: 'link', + state: 'home.templateeditor', + icon: 'template_editor', + link: '/static/svg/template.svg' + }, + { name: 'Preferences', type: 'link', @@ -420,7 +429,23 @@ function Menu(userService, $state, $rootScope, $log,datamodelService,customerSer icon: 'settings_applications' } ] - } + }, + + { + + name: 'templateEditor.templateEditor', + places: [ + { + name: 'templateEditor.templateEditor', + type: 'link', + state: 'home.templateeditor', + icon: 'template_editor', + link: '/static/svg/template.svg' + + } + ] + }, + ]; @@ -598,4 +623,4 @@ function Menu(userService, $state, $rootScope, $log,datamodelService,customerSer return $state.includes(section.state); } -} +} \ No newline at end of file diff --git a/ui/src/app/templateeditor/add-template.controller.js b/ui/src/app/templateeditor/add-template.controller.js new file mode 100644 index 000000000..3c458ee03 --- /dev/null +++ b/ui/src/app/templateeditor/add-template.controller.js @@ -0,0 +1,42 @@ +/* + * Copyright © 2016-2018 The Thingsboard Authors + * Modifications © 2017-2018 Hashmap, Inc + * + * 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 */ + + +/* eslint-enable import/no-unresolved, import/default */ + + +/*@ngInject*/ +export default function AddTemplateController($scope, types, $mdDialog, saveItemFunction) { + + var vm = this; + vm.item = {}; + vm.add = add; + vm.cancel = cancel; + + function cancel() { + $mdDialog.cancel(); + } + + function add() { + saveItemFunction(vm.item).then(function success(item) { + vm.item = item; + $scope.theForm.$setPristine(); + $mdDialog.hide(); + }); + } +} diff --git a/ui/src/app/templateeditor/add-template.tpl.html b/ui/src/app/templateeditor/add-template.tpl.html new file mode 100755 index 000000000..19705b39d --- /dev/null +++ b/ui/src/app/templateeditor/add-template.tpl.html @@ -0,0 +1,48 @@ + + +
+ +
+

templateEditor.add-template-text

+ +
+ + + +
+
+ + + +
+ +
+
+ + + + {{ 'action.add' | translate }} + + {{ 'action.cancel' | translate }} + +
+
\ No newline at end of file diff --git a/ui/src/app/templateeditor/index.js b/ui/src/app/templateeditor/index.js new file mode 100755 index 000000000..e2df4970b --- /dev/null +++ b/ui/src/app/templateeditor/index.js @@ -0,0 +1,35 @@ +/* + * Copyright © 2016-2018 The Thingsboard Authors + * Modifications © 2017-2018 Hashmap, Inc + * + * 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 uiRouter from 'angular-ui-router'; +import tempusApiTemplate from '../api/template.service'; +import tempusGrid from '../components/grid.directive'; +import TemplateRoutes from './template.routes'; +import {TemplateController} from './template.controller'; +import AddTemplateModelController from './add-template.controller'; +import TemplateDirective from './template.directive'; + +export default angular.module('tempus.templates', [ + uiRouter, + tempusApiTemplate, + tempusGrid +]) + .config(TemplateRoutes) + .controller('TemplateController',TemplateController) + .controller('AddTemplateModelController', AddTemplateModelController) + .directive('tbTemplate', TemplateDirective) + .name; diff --git a/ui/src/app/templateeditor/template-fieldset.tpl.html b/ui/src/app/templateeditor/template-fieldset.tpl.html new file mode 100644 index 000000000..c1e97c141 --- /dev/null +++ b/ui/src/app/templateeditor/template-fieldset.tpl.html @@ -0,0 +1,37 @@ + + +
+ + + +
+
templateEditor.name-required
+
+
+ + + + +
+
templateEditor.body-required
+
+
+
+
diff --git a/ui/src/app/templateeditor/template.controller.js b/ui/src/app/templateeditor/template.controller.js new file mode 100644 index 000000000..b940bd1e8 --- /dev/null +++ b/ui/src/app/templateeditor/template.controller.js @@ -0,0 +1,350 @@ +/* + * Copyright © 2016-2018 The Thingsboard Authors + * Modifications © 2017-2018 Hashmap, Inc + * + * 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, no-unused-vars */ +import './template.scss'; +import addTemplateEditor from './add-template.tpl.html'; + +/*@ngInject*/ +export function TemplateController($scope, userGroupService, $filter, $rootScope, $translate, templateService, $mdDialog, $document, $stateParams) { + + var vm = this; + vm.addTemplate = addTemplate; + vm.AddTemplateModelController = 'AddTemplateModelController'; + var customerId = $stateParams.customerId; + vm.copyEditor = copyEditor; + vm.deletetemplate = deletetemplate; + + var editorActionsList = []; + + editorActionsList.push({ + onAction: function($event, item) { + copyEditor($event, item); + }, + name: function() { + return $translate.instant('action.delete') + }, + details: function() { + return $translate.instant('templateEditor.copy') + }, + icon: "content_copy" + }); + + + editorActionsList.push({ + onAction: function($event, item) { + vm.grid.deleteItem($event, item); + }, + name: function() { + return $translate.instant('action.delete') + }, + details: function() { + return $translate.instant('templateEditor.delete') + }, + icon: "delete" + }); + + + + $scope.tableView = false; + + $scope.templates = { + count: 0, + data: [] + }; + + $scope.query = { + order: 'name', + limit: 15, + page: 1, + search: null + }; + + + + vm.templateEditorGridConfig = { + + deleteItemTitleFunc: deleteEditorTitle, + deleteItemContentFunc: deleteEditorText, + deleteItemsTitleFunc: deleteEditorsTitle, + deleteItemsActionTitleFunc: deleteEditorsActionTitle, + deleteItemsContentFunc: deleteEditorsText, + addItemController: 'AddTemplateModelController', + deleteItemFunc: deleteEditor, + addItemTemplateUrl: addTemplateEditor, + parentCtl: vm, + addIcon:"add", + getItemTitleFunc: getTemplateTitle, + actionsList: editorActionsList, + onGridInited: gridInited, + noItemsText: function() { + return $translate.instant('templateEditor.no-template-text') + }, + itemDetailsText: function() { + return $translate.instant('templateEditor.templateDetails') + }, + addItemText: function() { + return $translate.instant('templateEditor.add-template-text') + }, + entType: "templateEditor" + + } + + initController(); + + function initController() { + var fetchTemplateFunction = null; + var refreshUsersGroupParamsFunction = null; + var saveTemplateFunction = null; + var refreshTemplateParamsFunction = null; + fetchTemplateFunction = function(pageLink) { + + if($scope.query.page == 1){ + + return templateService.getTemplates(pageLink, 0); + } else { + + return templateService.getTemplates(pageLink, $scope.query.page - 1); + } + + }; + + saveTemplateFunction = function(template) { + + return templateService.saveTemplate(template); + }; + + refreshTemplateParamsFunction = function() { + return {"topIndex": vm.topIndex}; + }; + + + vm.templateEditorGridConfig.fetchItemsFunc = fetchTemplateFunction; + vm.templateEditorGridConfig.saveItemFunc = saveTemplateFunction; + vm.templateEditorGridConfig.refreshParamsFunc = refreshTemplateParamsFunction; + + } + + loadTableData(); + + function loadTableData() { + var promise = vm.templateEditorGridConfig.fetchItemsFunc({limit: $scope.query.limit, textSearch: ''}, false); + if(promise) { + promise.then(function success(items) { + var templateSortList = $filter('orderBy')(items.data, $scope.query.order); + + if ($scope.query.search != null) { + + templateSortList = $filter('filter')(items.data, function(data) { + if ($scope.query.search) { + return data.name.toLowerCase().indexOf($scope.query.search.toLowerCase()) > -1; + } else { + return true; + } + }); + templateSortList = $filter('orderBy')(templateSortList, $scope.query.order); + } + + var templatePaginatedata = templateSortList; + + $scope.templates = { + count: items.totalElements, + data: templatePaginatedata + }; + + }, + ); + + } + } + + + $scope.enterFilterMode = function() { + + $scope.query.search = ''; + //loadTableData(); + } + + $scope.exitFilterMode = function() { + + $scope.query.search = null; + loadTableData(); + } + + $scope.resetFilter = function() { + + $scope.query = { + order: 'name', + limit: $scope.query.limit, + page: 1, + search: null + }; + + loadTableData(); + vm.grid.refreshList(); + } + + vm.loadTableData = loadTableData; + $scope.$watch("query.search", function(newVal, prevVal) { + if (!angular.equals(newVal, prevVal) && $scope.query.search != null) { + + loadTableData(); + } + }); + + $scope.onReorder = function() { + loadTableData(); + } + + $scope.onPaginate = function(page) { + $scope.query.page = page; + loadTableData(); + } + + function deleteEditor(templateId) { + return templateService.deleteTemplate(templateId); + } + + function copyEditor($event,template) { + + if ($event) { + $event.stopPropagation(); + } + + vm.item = {}; + vm.templateName =[]; + var copyName = ''; + templateService.getAllTemplates().then(function (response) { + response.forEach(tempVal => { // + vm.templateName.push(tempVal.name); + }); + + var count = copyCount(template, vm.templateName); + copyName = "("+(count.highestcopy+1)+")"; + vm.item ={ + name:template.name.replace(/\(.*\)/, '')+copyName, + body:template.body + } + + templateService.saveTemplate(vm.item).then(function () { + vm.grid.refreshList(); + loadTableData(); + }); + + }); + } + + + function copyCount(template, templateList) { + var count = 0; + vm.countDetail ={}; + vm.copyCountHighest = []; + var regExp = /\(([^)]+)\)/; + var regex = /\([^)]*\)/g; + + for (var i = 0; i < templateList.length; i++) { + if (templateList[i].replace(/\(.*\)/, '') === template.name.replace(/\(.*\)/, '')) { + count++; + var matches = regExp.exec(templateList[i]); + if(matches == null){ + vm.copyCountHighest.push(0); + } else { + vm.copyCountHighest.push(Number(matches[1])); + } + } + } + var largestCopy = vm.copyCountHighest.sort((a,b)=>a-b).reverse()[0]; + vm.countDetail = {count:count,highestcopy:largestCopy}; + + return vm.countDetail; + } + + function deletetemplate($event,item) { + + var confirm = $mdDialog.confirm() + .targetEvent($event) + .title(deleteEditorTitle(item)) + .htmlContent(deleteEditorText(item)) + .ariaLabel($translate.instant('grid.delete-item')) + .cancel($translate.instant('action.no')) + .ok($translate.instant('action.yes')); + $mdDialog.show(confirm).then(function () { + vm.templateEditorGridConfig.deleteItemFunc(item.id.id).then(function success() { + $scope.resetFilter(); + vm.grid.refreshList(); + + }); + }); + + } + + + function getTemplateTitle(template) { + return template ? template.name : ''; + } + + + function deleteEditorTitle(template) { + return $translate.instant('templateEditor.delete-template-title', { + templateName: template.name + }); + } + + function deleteEditorText() { + return $translate.instant('templateEditor.delete-template-text'); + } + + function deleteEditorsTitle(selectedCount) { + return $translate.instant('templateEditor.delete-templates-title', { + count: selectedCount + }, 'messageformat'); + } + + function deleteEditorsActionTitle(selectedCount) { + return $translate.instant('templateEditor.delete-templates-action-title', { + count: selectedCount + }, 'messageformat'); + } + + function addTemplate($event) { + + $mdDialog.show({ + controller: vm.AddTemplateModelController, + controllerAs: 'vm', + templateUrl: addTemplateEditor, + parent: angular.element($document[0].body), + locals: {saveItemFunction: vm.templateEditorGridConfig.saveItemFunc}, + fullscreen: true, + targetEvent: $event + }).then(function() {$scope.resetFilter();}, function() {}); + } + + + $scope.templateDetailFunc = function($event,template) { + $rootScope.$emit("CallTableDetailTemplate", [$event, template]); + } + + + function deleteEditorsText() { + return $translate.instant('templateEditor.delete-templates-text'); + } + + function gridInited(grid) { + vm.grid = grid; + } + + +} \ No newline at end of file diff --git a/ui/src/app/templateeditor/template.directive.js b/ui/src/app/templateeditor/template.directive.js new file mode 100755 index 000000000..190238827 --- /dev/null +++ b/ui/src/app/templateeditor/template.directive.js @@ -0,0 +1,43 @@ +/* + * Copyright © 2016-2018 The Thingsboard Authors + * Modifications © 2017-2018 Hashmap, Inc + * + * 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 templateFieldsetTemplate from './template-fieldset.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +/*@ngInject*/ +export default function TemplateDirective($compile, $templateCache) { + var linker = function (scope, element) { + var template = $templateCache.get(templateFieldsetTemplate); + element.html(template); + + $compile(element.contents())(scope); + } + return { + restrict: "E", + link: linker, + scope: { + data: '=', + isEdit: '=', + applicationScope: '=', + theForm: '=', + } + }; +} + + diff --git a/ui/src/app/templateeditor/template.routes.js b/ui/src/app/templateeditor/template.routes.js new file mode 100755 index 000000000..1946dcf95 --- /dev/null +++ b/ui/src/app/templateeditor/template.routes.js @@ -0,0 +1,47 @@ +/* + * Copyright © 2016-2018 The Thingsboard Authors + * Modifications © 2017-2018 Hashmap, Inc + * + * 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 templateTemplate from './template.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +/*@ngInject*/ +export default function TemplateeditorRoutes($stateProvider) { + $stateProvider + .state('home.templateeditor', { + url: '/editor', + params: {'topIndex': 0}, + module: 'private', + auth: ['TENANT_ADMIN'], + views: { + "content@home": { + templateUrl: templateTemplate, + controller: 'TemplateController', + controllerAs: 'vm' + } + } + , + data: { + pageTitle: 'templateEditor.title' + }, + ncyBreadcrumb: { + label: '{"icon": "template_editor", "label": "templateEditor.title", "link": "/static/svg/template.svg"}' + } + }) + +} diff --git a/ui/src/app/templateeditor/template.scss b/ui/src/app/templateeditor/template.scss new file mode 100644 index 000000000..95fe5393c --- /dev/null +++ b/ui/src/app/templateeditor/template.scss @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2018 The Thingsboard Authors + * Modifications © 2017-2018 Hashmap, Inc + * + * 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-template-table { + background-color: white; +} + +.tb-template-container{ + margin-left: 3%; + margin-right: 3%; + width:93.9% +} + +.tb-template-toolbar { + margin-left: 3%; + width:93.9% +} + +.template-grid #tb-vertical-container{ + top: 30px !important; +} diff --git a/ui/src/app/templateeditor/template.tpl.html b/ui/src/app/templateeditor/template.tpl.html new file mode 100755 index 000000000..bbc9a8488 --- /dev/null +++ b/ui/src/app/templateeditor/template.tpl.html @@ -0,0 +1,115 @@ + + +
+ + + + +
+ + +
+ + search + + {{ 'action.search' | translate }} + + + + + + + + close + + {{ 'action.close' | translate }} + + +
+
+ + +
+ + + add + + {{ 'action.add' | translate }} + + + + search + + {{ 'action.search' | translate }} + + + + refresh + + {{ 'action.refresh' | translate }} + + +
+
+ + + + + + + + + + + + + + + + + + + + +
templateEditor.nametemplateEditor.createdDate
{{template.name}}{{template.createdTime | date : 'yyyy-MM-dd HH:mm:ss'}} + +
+
+ + + + + + + + + + + + + diff --git a/ui/src/svg/template.svg b/ui/src/svg/template.svg new file mode 100644 index 000000000..3538affa3 --- /dev/null +++ b/ui/src/svg/template.svg @@ -0,0 +1,9 @@ + + + + + + + + +